UNPKG

openhim-core

Version:

The OpenHIM core application that provides logging and routing of http requests

388 lines (315 loc) 12 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.findAndProcessAQueuedTask = findAndProcessAQueuedTask; exports.start = start; exports.stop = stop; exports.isRunning = isRunning; var _winston = require('winston'); var _winston2 = _interopRequireDefault(_winston); var _http = require('http'); var _http2 = _interopRequireDefault(_http); var _net = require('net'); var _net2 = _interopRequireDefault(_net); var _tasks = require('./model/tasks'); var _channels = require('./model/channels'); var _transactions = require('./model/transactions'); var _rerunUpdateTransactionTask = require('./middleware/rerunUpdateTransactionTask'); var rerunMiddleware = _interopRequireWildcard(_rerunUpdateTransactionTask); 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.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) { _winston2.default.error(`An error occurred while looking for rerun tasks: ${err}`); } else { _winston2.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); _winston2.default.info('Started rerun task processor'); return callback(); } function stop(callback) { live = false; const waitForActiveTasks = function () { if (activeTasks > 0) { return setTimeout(waitForActiveTasks, 500); } _winston2.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'; _winston2.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(); _winston2.default.info(`Round completed for rerun task #${task._id} - Task completed`); } else { task.status = result.status; _winston2.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) { _winston2.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; _winston2.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; _winston2.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) { _winston2.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); } _winston2.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); } _winston2.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: {} }; _winston2.default.info(`Rerun Transaction #${transaction._id} - HTTP Request is being sent...`); const req = _http2.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(); _winston2.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 _net2.default.Socket(); client.connect(channel.tcpPort, channel.tcpHost, () => { _winston2.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(); _winston2.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