UNPKG

openhim-core

Version:

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

329 lines (257 loc) 10.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.storeTransaction = storeTransaction; exports.storeResponse = storeResponse; exports.storeNonPrimaryResponse = storeNonPrimaryResponse; exports.setFinalStatus = setFinalStatus; exports.koaMiddleware = koaMiddleware; exports.transactionStatus = void 0; var _winston = _interopRequireDefault(require("winston")); var transactions = _interopRequireWildcard(require("../model/transactions")); var autoRetryUtils = _interopRequireWildcard(require("../autoRetry")); var utils = _interopRequireWildcard(require("../utils")); var _config = require("../config"); var metrics = _interopRequireWildcard(require("../metrics")); var _util = require("util"); 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 }; } const transactionStatus = { PROCESSING: 'Processing', SUCCESSFUL: 'Successful', COMPLETED: 'Completed', COMPLETED_W_ERR: 'Completed with error(s)', FAILED: 'Failed' }; exports.transactionStatus = transactionStatus; function copyMapWithEscapedReservedCharacters(map) { const escapedMap = {}; for (let k in map) { const v = map[k]; if (k.indexOf('.') > -1 || k.indexOf('$') > -1) { k = k.replace('.', '\uff0e').replace('$', '\uff04'); } escapedMap[k] = v; } return escapedMap; } function storeTransaction(ctx, done) { _winston.default.info('Storing request metadata for inbound transaction'); ctx.requestTimestamp = new Date(); const headers = copyMapWithEscapedReservedCharacters(ctx.header); const tx = new transactions.TransactionModel({ status: transactionStatus.PROCESSING, clientID: ctx.authenticated != null ? ctx.authenticated._id : undefined, channelID: ctx.authorisedChannel._id, clientIP: ctx.ip, request: { host: ctx.host != null ? ctx.host.split(':')[0] : undefined, port: ctx.host != null ? ctx.host.split(':')[1] : undefined, path: ctx.path, headers, querystring: ctx.querystring, body: ctx.body, method: ctx.method, timestamp: ctx.requestTimestamp } }); if (ctx.parentID && ctx.taskID) { tx.parentID = ctx.parentID; tx.taskID = ctx.taskID; } if (ctx.currentAttempt) { tx.autoRetryAttempt = ctx.currentAttempt; } // check if channel request body is false and remove - or if request body is empty if (ctx.authorisedChannel.requestBody === false || tx.request.body === '') { // reset request body tx.request.body = ''; // check if method is POST|PUT|PATCH - rerun not possible without request body if (ctx.method === 'POST' || ctx.method === 'PUT' || ctx.method === 'PATCH') { tx.canRerun = false; } } if (utils.enforceMaxBodiesSize(ctx, tx.request)) { tx.canRerun = false; } return tx.save((err, tx) => { if (err) { _winston.default.error(`Could not save transaction metadata: ${err}`); return done(err); } else { ctx.transactionId = tx._id; ctx.header['X-OpenHIM-TransactionID'] = tx._id.toString(); return done(null, tx); } }); } function storeResponse(ctx, done) { const headers = copyMapWithEscapedReservedCharacters(ctx.response.header); const res = { status: ctx.response.status, headers, body: !ctx.response.body ? '' : ctx.response.body.toString(), timestamp: ctx.response.timestamp }; // check if channel response body is false and remove if (ctx.authorisedChannel.responseBody === false) { // reset request body - primary route res.body = ''; } const update = { response: res, error: ctx.error, orchestrations: [] }; utils.enforceMaxBodiesSize(ctx, update.response); if (ctx.mediatorResponse) { if (ctx.mediatorResponse.orchestrations) { update.orchestrations.push(...truncateOrchestrationBodies(ctx, ctx.mediatorResponse.orchestrations)); } if (ctx.mediatorResponse.properties) { update.properties = ctx.mediatorResponse.properties; } } if (ctx.orchestrations) { update.orchestrations.push(...truncateOrchestrationBodies(ctx, ctx.orchestrations)); } return transactions.TransactionModel.findOneAndUpdate({ _id: ctx.transactionId }, update, { runValidators: true }, (err, tx) => { if (err) { _winston.default.error(`Could not save response metadata for transaction: ${ctx.transactionId}. ${err}`); return done(err); } if (tx === undefined || tx === null) { _winston.default.error(`Could not find transaction: ${ctx.transactionId}`); return done(err); } _winston.default.info(`stored primary response for ${tx._id}`); return done(); }); } function truncateOrchestrationBodies(ctx, orchestrations) { return orchestrations.map(orch => { const truncatedOrchestration = Object.assign({}, orch); if (truncatedOrchestration.request && truncatedOrchestration.request.body) { utils.enforceMaxBodiesSize(ctx, truncatedOrchestration.request); } if (truncatedOrchestration.response && truncatedOrchestration.response.body) { utils.enforceMaxBodiesSize(ctx, truncatedOrchestration.response); } return truncatedOrchestration; }); } function storeNonPrimaryResponse(ctx, route, done) { // check if channel response body is false and remove if (ctx.authorisedChannel.responseBody === false) { route.response.body = ''; } if (ctx.transactionId != null) { if ((route.request != null ? route.request.body : undefined) != null) { utils.enforceMaxBodiesSize(ctx, route.request); } if ((route.response != null ? route.response.body : undefined) != null) { utils.enforceMaxBodiesSize(ctx, route.response); } transactions.TransactionModel.findByIdAndUpdate(ctx.transactionId, { $push: { routes: route } }, (err, tx) => { if (err) { _winston.default.error(err); } return done(tx); }); } else { return _winston.default.error('the request has no transactionId'); } } /** * Set the status of the transaction based on the outcome of all routes. * * If the primary route responded in the mediator format and included a status * then that overrides all other status calculations. * * This should only be called once all routes have responded. */ function setFinalStatus(ctx, callback) { let transactionId = ''; if (ctx.request != null && ctx.request.header != null && ctx.request.header['X-OpenHIM-TransactionID'] != null) { transactionId = ctx.request.header['X-OpenHIM-TransactionID']; } else { transactionId = ctx.transactionId.toString(); } return transactions.TransactionModel.findById(transactionId, (err, tx) => { if (err) { return callback(err); } const update = {}; if ((ctx.mediatorResponse != null ? ctx.mediatorResponse.status : undefined) != null) { _winston.default.debug(`The transaction status has been set to ${ctx.mediatorResponse.status} by the mediator`); update.status = ctx.mediatorResponse.status; } else { let routeFailures = false; let routeSuccess = true; if (ctx.routes) { for (const route of Array.from(ctx.routes)) { if (route.response.status >= 500 && route.response.status <= 599) { routeFailures = true; } if (!(route.response.status >= 200 && route.response.status <= 299)) { routeSuccess = false; } } } if (ctx.response.status >= 500 && ctx.response.status <= 599) { tx.status = transactionStatus.FAILED; } else { if (routeFailures) { tx.status = transactionStatus.COMPLETED_W_ERR; } if (ctx.response.status >= 200 && ctx.response.status <= 299 && routeSuccess) { tx.status = transactionStatus.SUCCESSFUL; } if (ctx.response.status >= 400 && ctx.response.status <= 499 && routeSuccess) { tx.status = transactionStatus.COMPLETED; } } // In all other cases mark as completed if (tx.status === 'Processing') { tx.status = transactionStatus.COMPLETED; } ctx.transactionStatus = tx.status; _winston.default.info(`Final status for transaction ${tx._id} : ${tx.status}`); update.status = tx.status; } if (ctx.autoRetry != null) { if (!autoRetryUtils.reachedMaxAttempts(tx, ctx.authorisedChannel)) { update.autoRetry = ctx.autoRetry; } else { update.autoRetry = false; } } transactions.TransactionModel.findByIdAndUpdate(transactionId, update, { new: true }, (err, tx) => { if (err) { return callback(err); } callback(null, tx); // queue for autoRetry if (update.autoRetry) { autoRetryUtils.queueForRetry(tx); } // Asynchronously record transaction metrics metrics.recordTransactionMetrics(tx).catch(err => { _winston.default.error('Recording transaction metrics failed', err); }); }); }); } async function koaMiddleware(ctx, next) { const saveTransaction = (0, _util.promisify)(storeTransaction); await saveTransaction(ctx); await next(); storeResponse(ctx, () => {}); } //# sourceMappingURL=messageStore.js.map