openhim-core
Version:
The OpenHIM core application that provides logging and routing of http requests
266 lines (220 loc) • 7.86 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.popTransaction = popTransaction;
exports.notifyMasterToStartTCPServer = notifyMasterToStartTCPServer;
exports.startupTCPServer = startupTCPServer;
exports.startupServers = startupServers;
exports.stopServers = stopServers;
exports.notifyMasterToStopTCPServer = notifyMasterToStopTCPServer;
exports.stopServerForChannel = stopServerForChannel;
var _http = require('http');
var _http2 = _interopRequireDefault(_http);
var _net = require('net');
var _net2 = _interopRequireDefault(_net);
var _tls = require('tls');
var _tls2 = _interopRequireDefault(_tls);
var _winston = require('winston');
var _winston2 = _interopRequireDefault(_winston);
var _channels = require('./model/channels');
var Channels = _interopRequireWildcard(_channels);
var _tlsAuthentication = require('./middleware/tlsAuthentication');
var tlsAuthentication = _interopRequireWildcard(_tlsAuthentication);
var _config = require('./config');
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
_config.config.tcpAdapter = _config.config.get('tcpAdapter');
const { ChannelModel } = Channels;
let tcpServers = [];
let newKey = 0;
const datastore = {};
process.on('message', msg => {
if (msg.type === 'start-tcp-channel') {
_winston2.default.debug(`Recieved message to start tcp channel: ${msg.channelID}`);
return exports.startupTCPServer(msg.channelID, () => {});
} else if (msg.type === 'stop-tcp-channel') {
_winston2.default.debug(`Recieved message to stop tcp channel: ${msg.channelID}`);
return exports.stopServerForChannel(msg.channelID, () => {});
}
});
function popTransaction(key) {
const res = datastore[`${key}`];
delete datastore[`${key}`];
return res;
}
function startListening(channel, tcpServer, host, port, callback) {
tcpServer.listen(port, host, () => {
tcpServers.push({ channelID: channel._id, server: tcpServer });
return callback(null);
});
return tcpServer.on('error', err => _winston2.default.error(`${err} Host: ${host} Port: ${port}`));
}
function notifyMasterToStartTCPServer(channelID, callback) {
_winston2.default.debug(`Sending message to master to start tcp channel: ${channelID}`);
return process.send({
type: 'start-tcp-channel',
channelID
});
}
function startupTCPServer(channelID, callback) {
for (const existingServer of Array.from(tcpServers)) {
// server already running for channel
if (existingServer.channelID.equals(channelID)) {
return callback(null);
}
}
const handler = sock => ChannelModel.findById(channelID, (err, channel) => {
if (err) {
return _winston2.default.error(err);
}
sock.on('data', data => adaptSocketRequest(channel, sock, `${data}`));
return sock.on('error', err => _winston2.default.error(err));
});
return ChannelModel.findById(channelID, (err, channel) => {
if (err) {
return callback(err);
}
const host = channel.tcpHost || '0.0.0.0';
const port = channel.tcpPort;
if (!port) {
return callback(new Error(`Channel ${channel.name} (${channel._id}): TCP port not defined`));
}
if (channel.type === 'tls') {
return tlsAuthentication.getServerOptions(true, (err, options) => {
if (err) {
return callback(err);
}
const tcpServer = _tls2.default.createServer(options, handler);
return startListening(channel, tcpServer, host, port, err => {
if (err) {
return callback(err);
} else {
_winston2.default.info(`Channel ${channel.name} (${channel._id}): TLS server listening on port ${port}`);
return callback(null);
}
});
});
} else if (channel.type === 'tcp') {
const tcpServer = _net2.default.createServer(handler);
return startListening(channel, tcpServer, host, port, err => {
if (err) {
return callback(err);
} else {
_winston2.default.info(`Channel ${channel.name} (${channel._id}): TCP server listening on port ${port}`);
return callback(null);
}
});
} else {
return callback(new Error(`Cannot handle ${channel.type} channels`));
}
});
}
// Startup a TCP server for each TCP channel
function startupServers(callback) {
return ChannelModel.find({ $or: [{ type: 'tcp' }, { type: 'tls' }] }, (err, channels) => {
if (err) {
return callback(err);
}
const promises = [];
Array.from(channels).forEach(channel => {
if (Channels.isChannelEnabled(channel)) {
const promise = new Promise((resolve, reject) => {
exports.startupTCPServer(channel._id, err => {
if (err) {
return reject(err);
}
return resolve();
});
});
return promises.push(promise);
}
});
return Promise.all(promises).then(() => callback(null)).catch(callback);
});
}
function adaptSocketRequest(channel, sock, socketData) {
const options = {
hostname: _config.config.tcpAdapter.httpReceiver.host,
port: _config.config.tcpAdapter.httpReceiver.httpPort,
path: '/',
method: 'POST'
};
const req = _http2.default.request(options, res => {
let response = '';
res.on('data', data => {
response += data;
});
return res.on('end', () => {
if (sock.writable) {
return sock.write(response);
}
});
});
req.on('error', err => _winston2.default.error(err));
// don't write the actual data to the http receiver
// instead send a reference through (see popTransaction)
datastore[`${newKey}`] = {};
datastore[`${newKey}`].data = socketData;
datastore[`${newKey}`].channel = channel;
req.write(`${newKey}`);
newKey++;
// in case we've been running for a couple thousand years
if (newKey === Number.MAX_VALUE) {
newKey = 0;
}
return req.end();
}
function stopTCPServers(servers, callback) {
const promises = Array.from(servers).map(server => {
return new Promise((resolve, reject) => {
server.server.close(err => {
if (err) {
return reject(err);
} else {
_winston2.default.info(`Channel ${server.channelID}: Stopped TCP/TLS server`);
return resolve();
}
});
});
});
Promise.all(promises).then(() => callback()).catch(callback);
}
function stopServers(callback) {
stopTCPServers(tcpServers, err => {
if (err) {
_winston2.default.error('Could not close tcp server', err);
return callback(err);
}
tcpServers = [];
return callback();
});
}
function notifyMasterToStopTCPServer(channelID, callback) {
_winston2.default.debug(`Sending message to master to stop tcp channel: ${channelID}`);
return process.send({
type: 'stop-tcp-channel',
channelID
});
}
function stopServerForChannel(channelID, callback) {
let server = null;
const notStoppedTcpServers = [];
for (const serverDetails of Array.from(tcpServers)) {
if (serverDetails.channelID.equals(channelID)) {
server = serverDetails;
} else {
// push all except the server we're stopping
notStoppedTcpServers.push(serverDetails);
}
}
if (!server) {
return callback(new Error(`Server for channel ${channelID} not running`));
}
tcpServers = notStoppedTcpServers;
return stopTCPServers([server], callback);
}
if (process.env.NODE_ENV === 'test') {
exports.tcpServers = tcpServers;
}
//# sourceMappingURL=tcpAdapter.js.map