openhim-core
Version:
The OpenHIM core application that provides logging and routing of http requests
402 lines (315 loc) • 12.6 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.findAndProcessAQueuedTask = findAndProcessAQueuedTask;
exports.start = start;
exports.stop = stop;
exports.isRunning = isRunning;
var _winston = _interopRequireDefault(require("winston"));
var _http = _interopRequireDefault(require("http"));
var _net = _interopRequireDefault(require("net"));
var _tasks = require("./model/tasks");
var _channels = require("./model/channels");
var _transactions = require("./model/transactions");
var rerunMiddleware = _interopRequireWildcard(require("./middleware/rerunUpdateTransactionTask"));
var _config = require("./config");
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
_config.config.rerun = _config.config.get('rerun');
let live = false;
let activeTasks = 0; // TODO : This needs to be converted to an event emitter or an observable
async function findAndProcessAQueuedTask() {
let task;
try {
task = await _tasks.TaskModel.findOneAndUpdate({
status: 'Queued'
}, {
status: 'Processing'
}, {
new: true
});
if (task != null) {
activeTasks++;
await processNextTaskRound(task);
activeTasks--;
}
} catch (err) {
if (task == null) {
_winston.default.error(`An error occurred while looking for rerun tasks: ${err}`);
} else {
_winston.default.error(`An error occurred while processing rerun task ${task._id}: ${err}`);
}
activeTasks--;
}
}
function rerunTaskProcessor() {
if (live) {
findAndProcessAQueuedTask();
return setTimeout(rerunTaskProcessor, _config.config.rerun.processor.pollPeriodMillis);
}
}
function start(callback) {
live = true;
setTimeout(rerunTaskProcessor, _config.config.rerun.processor.pollPeriodMillis);
_winston.default.info('Started rerun task processor');
return callback();
}
function stop(callback) {
live = false;
const waitForActiveTasks = function () {
if (activeTasks > 0) {
return setTimeout(waitForActiveTasks, 500);
}
_winston.default.info('Stopped rerun task processor');
return callback();
};
return waitForActiveTasks();
}
function isRunning() {
return live;
}
async function finalizeTaskRound(task) {
const result = await _tasks.TaskModel.findOne({
_id: task._id
}, {
status: 1
});
if (result.status === 'Processing' && task.remainingTransactions !== 0) {
task.status = 'Queued';
_winston.default.info(`Round completed for rerun task #${task._id} - ${task.remainingTransactions} transactions remaining`);
} else if (task.remainingTransactions === 0) {
task.status = 'Completed';
task.completedDate = new Date();
_winston.default.info(`Round completed for rerun task #${task._id} - Task completed`);
} else {
task.status = result.status;
_winston.default.info(`Round completed for rerun task #${task._id} - Task has been ${result.status}`);
}
await task.save();
}
/**
* Process a task.
*
* Tasks are processed in rounds:
* Each round consists of processing n transactions where n is between 1 and the task's batchSize,
* depending on how many transactions are left to process.
*
* When a round completes, the task will be marked as 'Queued' if it still has transactions remaining.
* The next available core instance will then pick up the task again for the next round.
*
* This model allows the instance the get updated information regarding the task in between rounds:
* i.e. if the server has been stopped, if the task has been paused, etc.
*/
async function processNextTaskRound(task) {
_winston.default.debug(`Processing next task round: total transactions = ${task.totalTransactions}, remainingTransactions = ${task.remainingTransactions}`);
const nextI = task.transactions.length - task.remainingTransactions;
const transactions = Array.from(task.transactions.slice(nextI, nextI + task.batchSize));
const promises = transactions.map(transaction => {
return new Promise(resolve => {
rerunTransaction(transaction.tid, task._id, (err, response) => {
if (err) {
transaction.tstatus = 'Failed';
transaction.error = err;
_winston.default.error(`An error occurred while rerunning transaction ${transaction.tid} for task ${task._id}: ${err}`);
} else if ((response != null ? response.status : undefined) === 'Failed') {
transaction.tstatus = 'Failed';
transaction.error = response.message;
_winston.default.error(`An error occurred while rerunning transaction ${transaction.tid} for task ${task._id}: ${err}`);
} else {
transaction.tstatus = 'Completed';
}
task.remainingTransactions--;
return resolve();
});
transaction.tstatus = 'Processing';
});
});
await Promise.all(promises);
try {
await task.save();
} catch (err) {
_winston.default.error(`Failed to save current task while processing round: taskID=${task._id}, err=${err}`, err);
}
return finalizeTaskRound(task);
}
function rerunTransaction(transactionID, taskID, callback) {
rerunGetTransaction(transactionID, (err, transaction) => {
if (err) {
return callback(err);
} // setup the option object for the HTTP Request
return _channels.ChannelModel.findById(transaction.channelID, (err, channel) => {
if (err) {
return callback(err);
}
_winston.default.info(`Rerunning ${channel.type} transaction`);
if (channel.type === 'http' || channel.type === 'polling') {
rerunSetHTTPRequestOptions(transaction, taskID, (err, options) => {
if (err) {
return callback(err);
} // Run the HTTP Request with details supplied in options object
return rerunHttpRequestSend(options, transaction, (err, HTTPResponse) => callback(err, HTTPResponse));
});
}
if (channel.type === 'tcp' || channel.type === 'tls') {
return rerunTcpRequestSend(channel, transaction, (err, TCPResponse) => {
if (err) {
return callback(err);
} // Update original
const ctx = {
parentID: transaction._id,
transactionId: transactionID,
transactionStatus: TCPResponse.status,
taskID
};
return rerunMiddleware.updateOriginalTransaction(ctx, err => {
if (err) {
return callback(err);
}
return rerunMiddleware.updateTask(ctx, callback);
});
});
}
});
});
}
function rerunGetTransaction(transactionID, callback) {
_transactions.TransactionModel.findById(transactionID, (err, transaction) => {
if (transaction == null) {
return callback(new Error(`Transaction ${transactionID} could not be found`), null);
} // check if 'canRerun' property is false - reject the rerun
if (!transaction.canRerun) {
err = new Error(`Transaction ${transactionID} cannot be rerun as there isn't enough information about the request`);
return callback(err, null);
} // send the transactions data in callback
return callback(null, transaction);
});
}
/**
* Construct HTTP options to be sent #
*/
function rerunSetHTTPRequestOptions(transaction, taskID, callback) {
if (transaction == null) {
const err = new Error('An empty Transaction object was supplied. Aborting HTTP options configuration');
return callback(err, null);
}
_winston.default.info(`Rerun Transaction #${transaction._id} - HTTP Request options being configured`);
const options = {
hostname: _config.config.rerun.host,
port: _config.config.rerun.httpPort,
path: transaction.request.path,
method: transaction.request.method,
headers: transaction.request.headers || {}
};
if (transaction.clientID) {
options.headers.clientID = transaction.clientID;
}
options.headers.parentID = transaction._id;
options.headers.taskID = taskID;
if (transaction.request.querystring) {
options.path += `?${transaction.request.querystring}`;
}
return callback(null, options);
}
/**
* Construct HTTP options to be sent #
*/
/**
* Function for sending HTTP Request #
*/
function rerunHttpRequestSend(options, transaction, callback) {
let err;
if (options == null) {
err = new Error('An empty \'Options\' object was supplied. Aborting HTTP Send Request');
return callback(err, null);
}
if (transaction == null) {
err = new Error('An empty \'Transaction\' object was supplied. Aborting HTTP Send Request');
return callback(err, null);
}
const response = {
body: '',
transaction: {}
};
_winston.default.info(`Rerun Transaction #${transaction._id} - HTTP Request is being sent...`);
const req = _http.default.request(options, res => {
res.on('data', chunk => {
// response data
response.body += chunk;
});
return res.on('end', err => {
if (err) {
response.transaction.status = 'Failed';
} else {
response.transaction.status = 'Completed';
}
response.status = res.statusCode;
response.message = res.statusMessage;
response.headers = res.headers;
response.timestamp = new Date();
_winston.default.info(`Rerun Transaction #${transaction._id} - HTTP Response has been captured`);
return callback(null, response);
});
});
req.on('error', err => {
// update the status of the transaction that was processed to indicate it failed to process
if (err) {
response.transaction.status = 'Failed';
}
response.status = 500;
response.message = 'Internal Server Error';
response.timestamp = new Date();
return callback(null, response);
}); // write data to request body
if (transaction.request.method === 'POST' || transaction.request.method === 'PUT') {
if (transaction.request.body != null) {
req.write(transaction.request.body);
}
}
return req.end();
}
function rerunTcpRequestSend(channel, transaction, callback) {
const response = {
body: '',
transaction: {}
};
const client = new _net.default.Socket();
client.connect(channel.tcpPort, channel.tcpHost, () => {
_winston.default.info(`Rerun Transaction ${transaction._id}: TCP connection established`);
client.end(transaction.request.body);
});
client.on('data', data => {
response.body += data;
});
client.on('end', data => {
response.status = 200;
response.transaction.status = 'Completed';
response.message = '';
response.headers = {};
response.timestamp = new Date();
_winston.default.info(`Rerun Transaction #${transaction._id} - TCP Response has been captured`);
callback(null, response);
});
return client.on('error', err => {
// update the status of the transaction that was processed to indicate it failed to process
if (err) {
response.transaction.status = 'Failed';
}
response.status = 500;
response.message = 'Internal Server Error';
response.timestamp = new Date();
return callback(null, response);
});
}
/**
* Export these functions when in the "test" environment #
*/
if (process.env.NODE_ENV === 'test') {
exports.rerunGetTransaction = rerunGetTransaction;
exports.rerunSetHTTPRequestOptions = rerunSetHTTPRequestOptions;
exports.rerunHttpRequestSend = rerunHttpRequestSend;
exports.rerunTcpRequestSend = rerunTcpRequestSend;
exports.findAndProcessAQueuedTask = findAndProcessAQueuedTask;
}
//# sourceMappingURL=tasks.js.map