openhim-core
Version:
The OpenHIM core application that provides logging and routing of http requests
329 lines (257 loc) • 10.2 kB
JavaScript
;
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