openhim-core
Version:
The OpenHIM core application that provides logging and routing of http requests
348 lines (284 loc) • 10.7 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.transactionStatus = undefined;
exports.storeTransaction = storeTransaction;
exports.storeResponse = storeResponse;
exports.storeNonPrimaryResponse = storeNonPrimaryResponse;
exports.setFinalStatus = setFinalStatus;
exports.koaMiddleware = koaMiddleware;
var _statsdClient = require('statsd-client');
var _statsdClient2 = _interopRequireDefault(_statsdClient);
var _os = require('os');
var _os2 = _interopRequireDefault(_os);
var _winston = require('winston');
var _winston2 = _interopRequireDefault(_winston);
var _lodash = require('lodash');
var _lodash2 = _interopRequireDefault(_lodash);
var _transactions = require('../model/transactions');
var transactions = _interopRequireWildcard(_transactions);
var _autoRetry = require('../autoRetry');
var autoRetryUtils = _interopRequireWildcard(_autoRetry);
var _utils = require('../utils');
var utils = _interopRequireWildcard(_utils);
var _config = require('../config');
var _stats = require('../stats');
var stats = _interopRequireWildcard(_stats);
var _metrics = require('../metrics');
var metrics = _interopRequireWildcard(_metrics);
var _util = require('util');
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.statsd = _config.config.get('statsd');
const statsdServer = _config.config.get('statsd');
const application = _config.config.get('application');
const domain = `${_os2.default.hostname()}.${application.name}.appMetrics`;
const sdc = new _statsdClient2.default(statsdServer);
const transactionStatus = exports.transactionStatus = {
PROCESSING: 'Processing',
SUCCESSFUL: 'Successful',
COMPLETED: 'Completed',
COMPLETED_W_ERR: 'Completed with error(s)',
FAILED: 'Failed'
};
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) {
_winston2.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) {
_winston2.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);
// Set status from mediator
if ((ctx.mediatorResponse != null ? ctx.mediatorResponse.status : undefined) != null) {
update.status = ctx.mediatorResponse.status;
}
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) {
_winston2.default.error(`Could not save response metadata for transaction: ${ctx.transactionId}. ${err}`);
return done(err);
}
if (tx === undefined || tx === null) {
_winston2.default.error(`Could not find transaction: ${ctx.transactionId}`);
return done(err);
}
_winston2.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);
}
return transactions.TransactionModel.findByIdAndUpdate(ctx.transactionId, { $push: { routes: route } }, (err, tx) => {
if (err) {
_winston2.default.error(err);
}
return done(tx);
});
} else {
return _winston2.default.error('the request has no transactionId');
}
}
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) {
_winston2.default.info(`The transaction status has been set to ${ctx.mediatorResponse.status} by the mediator`);
} 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;
_winston2.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;
}
}
if (_lodash2.default.isEmpty(update)) {
return callback(tx);
} // nothing to do
transactions.TransactionModel.findByIdAndUpdate(transactionId, update, { new: true }, (err, tx) => {
if (err) {
return callback(err);
}
callback(tx);
// queue for autoRetry
if (update.autoRetry) {
autoRetryUtils.queueForRetry(tx);
}
if (_config.config.statsd.enabled) {
stats.incrementTransactionCount(ctx, () => {});
return stats.measureTransactionDuration(ctx, () => {});
}
try {
metrics.recordTransactionMetrics(tx);
} catch (err) {
_winston2.default.error('Recording transaction metrics failed', err);
}
});
});
}
async function koaMiddleware(ctx, next) {
let startTime;
if (statsdServer.enabled) {
startTime = new Date();
}
const saveTransaction = (0, _util.promisify)(storeTransaction);
await saveTransaction(ctx);
if (statsdServer.enabled) {
sdc.timing(`${domain}.messageStoreMiddleware.storeTransaction`, startTime);
}
await next();
if (statsdServer.enabled) {
startTime = new Date();
}
storeResponse(ctx, () => {});
if (statsdServer.enabled) {
return sdc.timing(`${domain}.messageStoreMiddleware.storeResponse`, startTime);
}
}
//# sourceMappingURL=messageStore.js.map