openhim-core
Version:
The OpenHIM core application that provides logging and routing of http requests
773 lines (723 loc) • 24.9 kB
JavaScript
var Agenda, Keystore, Q, User, activeApiConnections, activeHttpConnections, activeHttpsConnections, activePollingConnections, activeRerunConnections, activeTcpConnections, addWorker, agenda, alerts, apiHttpsServer, auditTcpServer, auditTlsServer, auditUDPServer, auditing, cluster, clusterArg, clusterSize, config, connectionATNA, connectionDefault, defaultKeystore, dgram, ensureKeystore, ensureRootUser, fs, http, httpServer, https, httpsServer, koaApi, koaMiddleware, logger, lookupServerPorts, mongoose, nconf, net, numCPUs, path, pem, polling, pollingServer, ports, ref, reports, rerunServer, rootUser, startAgenda, startApiServer, startAuditTcpTlsServer, startAuditUDPServer, startHttpServer, startHttpsServer, startPollingServer, startRerunServer, startTCPServersAndHttpReceiver, stop, stopAgenda, stopTasksProcessor, tasks, tcpAdapter, tcpHttpReceiver, tls, tlsAuthentication, trackConnection, upgradeDB, uuid;
require('source-map-support').install();
path = require('path');
global.appRoot = path.join(path.resolve(__dirname), '..');
config = require("./config/config");
config.mongo = config.get('mongo');
config.authentication = config.get('authentication');
config.router = config.get('router');
config.api = config.get('api');
config.rerun = config.get('rerun');
config.tcpAdapter = config.get('tcpAdapter');
config.logger = config.get('logger');
config.alerts = config.get('alerts');
config.polling = config.get('polling');
config.reports = config.get('reports');
config.auditing = config.get('auditing');
config.agenda = config.get('agenda');
mongoose = require("mongoose");
exports.connectionDefault = connectionDefault = mongoose.createConnection(config.mongo.url);
exports.connectionATNA = connectionATNA = mongoose.createConnection(config.mongo.atnaUrl);
fs = require('fs');
http = require('http');
https = require('https');
tls = require('tls');
net = require('net');
dgram = require('dgram');
koaMiddleware = require("./koaMiddleware");
koaApi = require("./koaApi");
tlsAuthentication = require("./middleware/tlsAuthentication");
uuid = require('node-uuid');
Q = require('q');
logger = require('winston');
logger.remove(logger.transports.Console);
cluster = require('cluster');
numCPUs = require('os').cpus().length;
nconf = require('nconf');
User = require('./model/users').User;
Keystore = require('./model/keystore').Keystore;
pem = require('pem');
Agenda = require('agenda');
alerts = require('./alerts');
reports = require('./reports');
polling = require('./polling');
tcpAdapter = require('./tcpAdapter');
auditing = require('./auditing');
tasks = require('./tasks');
upgradeDB = require('./upgradeDB');
clusterArg = nconf.get('cluster');
if (cluster.isMaster && !module.parent) {
logger.add(logger.transports.Console, {
colorize: true,
timestamp: true,
label: "master",
level: config.logger.level
});
if (clusterArg == null) {
clusterArg = 1;
}
if (clusterArg === 'auto') {
clusterSize = numCPUs;
} else {
clusterSize = clusterArg;
}
if (typeof clusterSize !== 'number' || clusterSize % 1 !== 0 || clusterSize < 1) {
throw new Error("invalid --cluster argument entered: " + clusterArg + ". Please enter a positive number or 'auto'.");
}
logger.info("Clustering the OpenHIM with " + clusterSize + " workers...");
addWorker = function() {
var worker;
worker = cluster.fork();
return worker.on('message', function(msg) {
var id, ref, ref1, ref2, results, results1, results2;
logger.debug("Message recieved from worker " + worker.id, msg);
if (msg.type === 'restart-all') {
logger.debug("Restarting all workers...");
ref = cluster.workers;
results = [];
for (id in ref) {
worker = ref[id];
logger.debug("Restarting worker " + worker.id + "...");
results.push(worker.send({
type: 'restart'
}));
}
return results;
} else if (msg.type === 'start-tcp-channel') {
logger.info("Starting TCP channel for channel: " + msg.channelID);
ref1 = cluster.workers;
results1 = [];
for (id in ref1) {
worker = ref1[id];
logger.debug("Starting TCP channel on worker " + worker.id + "...");
results1.push(worker.send(msg));
}
return results1;
} else if (msg.type === 'stop-tcp-channel') {
logger.info("Stopping TCP channel for channel: " + msg.channelID);
ref2 = cluster.workers;
results2 = [];
for (id in ref2) {
worker = ref2[id];
logger.debug("Stopping TCP channel on worker " + worker.id + "...");
results2.push(worker.send(msg));
}
return results2;
} else if (msg.type === 'get-uptime') {
return worker.send({
type: 'get-uptime',
masterUptime: process.uptime()
});
}
});
};
upgradeDB.upgradeDb(function() {
var i, j, ref;
for (i = j = 1, ref = clusterSize; 1 <= ref ? j <= ref : j >= ref; i = 1 <= ref ? ++j : --j) {
addWorker();
}
cluster.on('exit', function(worker, code, signal) {
logger.warn("worker " + worker.process.pid + " died");
if (!worker.suicide) {
return addWorker();
}
});
cluster.on('online', function(worker) {
return logger.info("worker with pid " + worker.process.pid + " is online");
});
return cluster.on('listening', function(worker, address) {
return logger.debug("worker " + worker.id + " is now connected to " + address.address + ":" + address.port);
});
});
} else {
logger.add(logger.transports.Console, {
colorize: true,
timestamp: true,
label: ((ref = cluster.worker) != null ? ref.id : void 0) != null ? "worker" + cluster.worker.id : void 0,
level: config.logger.level
});
httpServer = null;
httpsServer = null;
apiHttpsServer = null;
rerunServer = null;
tcpHttpReceiver = null;
pollingServer = null;
auditUDPServer = null;
auditTlsServer = null;
auditTcpServer = null;
activeHttpConnections = {};
activeHttpsConnections = {};
activeApiConnections = {};
activeRerunConnections = {};
activeTcpConnections = {};
activePollingConnections = {};
trackConnection = function(map, socket) {
var id;
id = uuid.v4();
map[id] = socket;
socket.on('close', function() {
map[id] = null;
return delete map[id];
});
return socket.on('error', function(err) {
return logger.error(err);
});
};
exports.isTcpHttpReceiverRunning = function() {
return tcpHttpReceiver != null;
};
rootUser = {
firstname: 'Super',
surname: 'User',
email: 'root@openhim.org',
passwordAlgorithm: 'sha512',
passwordHash: '943a856bba65aad6c639d5c8d4a11fc8bb7fe9de62ae307aec8cf6ae6c1faab722127964c71db4bdd2ea2cdf60c6e4094dcad54d4522ab2839b65ae98100d0fb',
passwordSalt: 'd9bcb40e-ae65-478f-962e-5e5e5e7d0a01',
groups: ['admin']
};
defaultKeystore = {
key: fs.readFileSync(appRoot + "/resources/certs/default/key.pem"),
cert: {
country: 'ZA',
state: 'KZN',
locality: 'Durban',
organization: 'OpenHIM Default Certificate',
organizationUnit: 'Default',
commonName: '*.openhim.org',
emailAddress: 'openhim-implementers@googlegroups.com',
validity: {
start: 1423810077000,
end: 3151810077000
},
data: fs.readFileSync(appRoot + "/resources/certs/default/cert.pem")
},
ca: []
};
agenda = null;
startAgenda = function() {
var defer;
defer = Q.defer();
agenda = new Agenda({
db: {
address: config.mongo.url
}
});
agenda.on("start", function(job) {
return logger.info("starting job: " + job.attrs.name + ", Last Ran at: " + job.attrs.lastRunAt);
});
agenda.on("fail", function(err, job) {
return logger.error("Job " + job.attrs.name + " failed with " + err.message);
});
agenda.on("complete", function(job) {
return logger.info("Job " + job.attrs.name + " has completed");
});
if (config.alerts.enableAlerts) {
alerts.setupAgenda(agenda);
}
if (config.reports.enableReports) {
reports.setupAgenda(agenda);
}
if (config.polling.enabled) {
polling.setupAgenda(agenda, function() {
return setTimeout(function() {
agenda.start();
defer.resolve();
return logger.info("Started agenda job scheduler");
}, config.agenda.startupDelay);
});
} else {
agenda.start();
defer.resolve();
}
return defer.promise;
};
stopAgenda = function() {
var defer;
defer = Q.defer();
agenda.stop(function() {
defer.resolve();
return logger.info("Stopped agenda job scheduler");
});
return defer.promise;
};
startHttpServer = function(httpPort, bindAddress, app) {
var deferred;
deferred = Q.defer();
httpServer = http.createServer(app.callback());
httpServer.setTimeout(config.router.timeout, function() {
return logger.info("HTTP socket timeout reached");
});
httpServer.listen(httpPort, bindAddress, function() {
logger.info("HTTP listening on port " + httpPort);
return deferred.resolve();
});
httpServer.on('error', function(err) {
return logger.error("An httpServer error occured: " + err);
});
httpServer.on('clientError', function(err) {
return logger.error("An httpServer clientError occured: " + err);
});
httpServer.on('connection', function(socket) {
return trackConnection(activeHttpConnections, socket);
});
return deferred.promise;
};
startHttpsServer = function(httpsPort, bindAddress, app) {
var deferred, mutualTLS;
deferred = Q.defer();
mutualTLS = config.authentication.enableMutualTLSAuthentication;
tlsAuthentication.getServerOptions(mutualTLS, function(err, options) {
if (err) {
return done(err);
}
httpsServer = https.createServer(options, app.callback());
httpsServer.setTimeout(config.router.timeout, function() {
return logger.info("HTTPS socket timeout reached");
});
httpsServer.listen(httpsPort, bindAddress, function() {
logger.info("HTTPS listening on port " + httpsPort);
return deferred.resolve();
});
httpsServer.on('error', function(err) {
return logger.error("An httpsServer error occured: " + err);
});
httpsServer.on('clientError', function(err) {
return logger.error("An httpsServer clientError occured: " + err);
});
return httpsServer.on('secureConnection', function(socket) {
return trackConnection(activeHttpsConnections, socket);
});
});
return deferred.promise;
};
ensureRootUser = function(callback) {
return User.findOne({
email: 'root@openhim.org'
}, function(err, user) {
if (!user) {
user = new User(rootUser);
return user.save(function(err) {
if (err) {
logger.error("Could not save root user: " + err);
return callback(err);
}
logger.info("Root user created.");
return callback();
});
} else {
return callback();
}
});
};
ensureKeystore = function(callback) {
return Keystore.findOne({}, function(err, keystore) {
if (keystore == null) {
keystore = new Keystore(defaultKeystore);
return keystore.save(function(err, keystore) {
if (err) {
logger.error("Could not save keystore: " + err);
return callback(err);
}
logger.info("Default keystore created.");
return callback();
});
} else {
return callback();
}
});
};
startApiServer = function(apiPort, bindAddress, app) {
var deferred, mutualTLS;
deferred = Q.defer();
mutualTLS = false;
tlsAuthentication.getServerOptions(mutualTLS, function(err, options) {
if (err) {
logger.error("Could not fetch https server options: " + err);
}
apiHttpsServer = https.createServer(options, app.callback());
apiHttpsServer.listen(apiPort, bindAddress, function() {
logger.info("API HTTPS listening on port " + apiPort);
return ensureRootUser(function() {
return deferred.resolve();
});
});
return apiHttpsServer.on('secureConnection', function(socket) {
return trackConnection(activeApiConnections, socket);
});
});
return deferred.promise;
};
startTCPServersAndHttpReceiver = function(tcpHttpReceiverPort, app) {
var defer;
defer = Q.defer();
tcpHttpReceiver = http.createServer(app.callback());
tcpHttpReceiver.listen(tcpHttpReceiverPort, config.tcpAdapter.httpReceiver.host, function() {
logger.info("HTTP receiver for Socket adapter listening on port " + tcpHttpReceiverPort);
return tcpAdapter.startupServers(function(err) {
if (err) {
logger.error(err);
}
return defer.resolve();
});
});
tcpHttpReceiver.on('connection', function(socket) {
return trackConnection(activeTcpConnections, socket);
});
return defer.promise;
};
startRerunServer = function(httpPort, app) {
var deferredHttp;
deferredHttp = Q.defer();
rerunServer = http.createServer(app.callback());
rerunServer.listen(httpPort, config.rerun.host, function() {
logger.info("Transaction Rerun HTTP listening on port " + httpPort);
return deferredHttp.resolve();
});
rerunServer.on('connection', function(socket) {
return trackConnection(activeRerunConnections, socket);
});
return deferredHttp.promise;
};
startPollingServer = function(pollingPort, app) {
var defer;
defer = Q.defer();
pollingServer = http.createServer(app.callback());
pollingServer.listen(pollingPort, config.polling.host, function(err) {
if (err) {
logger.error(err);
}
logger.info('Polling port listening on port ' + pollingPort);
return defer.resolve();
});
pollingServer.on('connection', function(socket) {
return trackConnection(activePollingConnections, socket);
});
return defer.promise;
};
startAuditUDPServer = function(auditUDPPort, bindAddress) {
var defer;
defer = Q.defer();
auditUDPServer = dgram.createSocket('udp4');
auditUDPServer.on('listening', function() {
logger.info("Auditing UDP server listening on port " + auditUDPPort);
return defer.resolve();
});
auditUDPServer.on('message', function(msg, rinfo) {
logger.info("[Auditing UDP] Received message from " + rinfo.address + ":" + rinfo.port);
return auditing.processAudit(msg, function() {
return logger.info("[Auditing UDP] Processed audit");
});
});
auditUDPServer.on('error', function(err) {
if (err.code === 'EADDRINUSE') {
return defer.resolve();
} else {
logger.error("UDP Audit server error: " + err, err);
return defer.reject(err);
}
});
auditUDPServer.bind({
port: auditUDPPort,
address: bindAddress,
exclusive: true
});
return defer.promise;
};
startAuditTcpTlsServer = function(type, auditPort, bindAddress) {
var defer, handler;
defer = Q.defer();
handler = function(sock) {
var length, message;
message = "";
length = 0;
sock.on('data', function(data) {
var lengthIndex, lengthValue;
message += data.toString();
if (length === 0 && message.indexOf(' ') !== -1) {
lengthIndex = message.indexOf(" ");
lengthValue = message.substr(0, lengthIndex);
length = parseInt(lengthValue.trim());
message = message.substr(lengthIndex + 1);
}
if (isNaN(length)) {
logger.info("[Auditing " + type + "] No length supplied");
sock.destroy();
}
if (length === message.length) {
logger.info("[Auditing " + type + "] Received message from " + sock.remoteAddress);
auditing.processAudit(message, function() {
return logger.info("[Auditing " + type + "] Processed audit");
});
message = "";
return length = 0;
}
});
return sock.on('error', function(err) {
return logger.error(err);
});
};
if (type === 'TLS') {
tlsAuthentication.getServerOptions(true, function(err, options) {
if (err) {
return callback(err);
}
auditTlsServer = tls.createServer(options, handler);
return auditTlsServer.listen(auditPort, bindAddress, function() {
logger.info("Auditing TLS server listening on port " + auditPort);
return defer.resolve();
});
});
} else if (type === 'TCP') {
auditTcpServer = net.createServer(handler);
auditTcpServer.listen(auditPort, bindAddress, function() {
logger.info("Auditing TCP server listening on port " + auditPort);
return defer.resolve();
});
}
return defer.promise;
};
exports.start = function(ports, done) {
var bindAddress, promises;
bindAddress = config.get('bindAddress');
logger.info("Starting OpenHIM server on " + bindAddress + "...");
promises = [];
return ensureKeystore(function() {
var defer;
if (ports.httpPort || ports.httpsPort) {
koaMiddleware.setupApp(function(app) {
if (ports.httpPort) {
promises.push(startHttpServer(ports.httpPort, bindAddress, app));
}
if (ports.httpsPort) {
return promises.push(startHttpsServer(ports.httpsPort, bindAddress, app));
}
});
}
if (ports.apiPort && config.api.enabled) {
koaApi.setupApp(function(app) {
return promises.push(startApiServer(ports.apiPort, bindAddress, app));
});
}
if (ports.rerunHttpPort) {
koaMiddleware.rerunApp(function(app) {
return promises.push(startRerunServer(ports.rerunHttpPort, app));
});
if (config.rerun.processor.enabled) {
defer = Q.defer();
tasks.start(function() {
return defer.resolve();
});
promises.push(defer.promise);
}
}
if (ports.tcpHttpReceiverPort) {
koaMiddleware.tcpApp(function(app) {
return promises.push(startTCPServersAndHttpReceiver(ports.tcpHttpReceiverPort, app));
});
}
if (ports.pollingPort) {
koaMiddleware.pollingApp(function(app) {
return promises.push(startPollingServer(ports.pollingPort, app));
});
}
if (ports.auditUDPPort) {
promises.push(startAuditUDPServer(ports.auditUDPPort, bindAddress));
}
if (ports.auditTlsPort) {
promises.push(startAuditTcpTlsServer('TLS', ports.auditTlsPort, bindAddress));
}
if (ports.auditTcpPort) {
promises.push(startAuditTcpTlsServer('TCP', ports.auditTcpPort, bindAddress));
}
promises.push(startAgenda());
return (Q.all(promises)).then(function() {
logger.info("OpenHIM server started: " + (new Date()));
return done();
});
});
};
stopTasksProcessor = function(callback) {
if (tasks.isRunning()) {
return tasks.stop(callback);
} else {
return callback();
}
};
exports.stop = stop = function(done) {
return stopTasksProcessor(function() {
var defer, err, key, promises, socket, stopServer;
promises = [];
stopServer = function(server, serverType) {
var deferred;
deferred = Q.defer();
server.close(function() {
logger.info("Stopped " + serverType + " server");
return deferred.resolve();
});
return deferred.promise;
};
if (httpServer) {
promises.push(stopServer(httpServer, 'HTTP'));
}
if (httpsServer) {
promises.push(stopServer(httpsServer, 'HTTPS'));
}
if (apiHttpsServer) {
promises.push(stopServer(apiHttpsServer, 'API HTTP'));
}
if (rerunServer) {
promises.push(stopServer(rerunServer, 'Rerun HTTP'));
}
if (pollingServer) {
promises.push(stopServer(pollingServer, 'Polling HTTP'));
}
if (agenda) {
promises.push(stopAgenda());
}
if (auditTlsServer) {
promises.push(stopServer(auditTlsServer, 'Audit TLS').promise);
}
if (auditTcpServer) {
promises.push(stopServer(auditTcpServer, 'Audit TCP').promise);
}
if (auditUDPServer) {
try {
auditUDPServer.close();
logger.info("Stopped Audit UDP server");
} catch (_error) {
err = _error;
}
}
if (tcpHttpReceiver) {
promises.push(stopServer(tcpHttpReceiver, 'TCP HTTP Receiver'));
defer = Q.defer();
tcpAdapter.stopServers(function() {
return defer.resolve();
});
promises.push(defer.promise);
}
for (key in activeHttpConnections) {
socket = activeHttpConnections[key];
socket.destroy();
}
for (key in activeHttpsConnections) {
socket = activeHttpsConnections[key];
socket.destroy();
}
for (key in activeApiConnections) {
socket = activeApiConnections[key];
socket.destroy();
}
for (key in activeRerunConnections) {
socket = activeRerunConnections[key];
socket.destroy();
}
for (key in activeTcpConnections) {
socket = activeTcpConnections[key];
socket.destroy();
}
for (key in activePollingConnections) {
socket = activePollingConnections[key];
socket.destroy();
}
return (Q.all(promises)).then(function() {
httpServer = null;
httpsServer = null;
apiHttpsServer = null;
rerunServer = null;
tcpHttpReceiver = null;
pollingServer = null;
auditUDPServer = null;
auditTlsServer = null;
auditTcpServer = null;
agenda = null;
logger.info('Server shutdown complete.');
return done();
});
});
};
lookupServerPorts = function() {
return {
httpPort: config.router.httpPort,
httpsPort: config.router.httpsPort,
apiPort: config.api.httpsPort,
rerunHttpPort: config.rerun.httpPort,
tcpHttpReceiverPort: config.tcpAdapter.httpReceiver.httpPort,
pollingPort: config.polling.pollingPort,
auditUDPPort: config.auditing.servers.udp.enabled ? config.auditing.servers.udp.port : void 0,
auditTlsPort: config.auditing.servers.tls.enabled ? config.auditing.servers.tls.port : void 0,
auditTcpPort: config.auditing.servers.tcp.enabled ? config.auditing.servers.tcp.port : void 0
};
};
if (!module.parent) {
ports = lookupServerPorts();
exports.start(ports, function() {
process.on('exit', stop);
process.on('SIGINT', function() {
return stop(process.exit);
});
process.on('SIGTERM', function() {
return stop(process.exit);
});
return process.on('message', function(msg) {
if (msg.type === 'restart') {
return exports.restartServer();
}
});
});
}
exports.restartServer = function(ports, done) {
if (typeof ports === 'function') {
done = ports;
ports = null;
}
if (typeof port === "undefined" || port === null) {
ports = lookupServerPorts();
}
return exports.stop(function() {
return exports.start(ports, function() {
return done();
});
});
};
exports.startRestartServerTimeout = function(done) {
if (cluster.isMaster) {
setTimeout(function() {
logger.debug('Master restarting itself...');
return exports.restartServer();
}, 2000);
} else {
setTimeout(function() {
logger.debug('Sending restart cluster message...');
return process.send({
type: 'restart-all'
});
}, 2000);
}
return done();
};
exports.getUptime = function(callback) {
var processEvent, uptime;
if (cluster.isMaster) {
uptime = {
master: process.uptime()
};
return callback(null, uptime);
} else {
process.send({
type: 'get-uptime'
});
processEvent = function(uptime) {
if (uptime.type === 'get-uptime') {
uptime = {
master: uptime.masterUptime
};
process.removeListener('message', processEvent);
return callback(null, uptime);
}
};
return process.on('message', processEvent);
}
};
}
//# sourceMappingURL=server.js.map