UNPKG

openhim-core

Version:

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

402 lines (315 loc) 12.6 kB
"use strict"; 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