vulcain-corejs
Version:
Vulcain micro-service framework
235 lines (233 loc) • 9.97 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments)).next());
});
};
const http = require("http");
const actions_1 = require("../pipeline/actions");
const query_1 = require("../pipeline/query");
const requestContext_1 = require("./requestContext");
const annotations_1 = require("../di/annotations");
const metrics_1 = require("../metrics/metrics");
const system_1 = require("./../configurations/globals/system");
const response_1 = require("../pipeline/response");
const guid = require('uuid');
class AbstractAdapter {
constructor(domainName, container) {
this.domainName = domainName;
this.container = container;
this.commandManager = new actions_1.CommandManager(container);
this.queryManager = new query_1.QueryManager(container);
this.testUser = container.get(annotations_1.DefaultServiceNames.TestUser, true);
this.domain = container.get(annotations_1.DefaultServiceNames.Domain);
this.metrics = container.get(annotations_1.DefaultServiceNames.Metrics);
let descriptors = this.container.get(annotations_1.DefaultServiceNames.ServiceDescriptors);
let hasAsyncTasks = descriptors.getDescriptions().hasAsyncTasks;
this.commandManager.startMessageBus(hasAsyncTasks);
}
calcDelayInMs(begin) {
// ts = [seconds, nanoseconds]
const ts = process.hrtime(begin);
// convert seconds to miliseconds and nanoseconds to miliseconds as well
return (ts[0] * 1000) + (ts[1] / 1000000);
}
startRequest(command) {
// util.log("Request : " + JSON.stringify(command)); // TODO remove sensible data
return process.hrtime();
}
createRequestContext(request) {
let ctx = new requestContext_1.RequestContext(this.container, requestContext_1.Pipeline.HttpRequest);
// Initialize headers & hostname
this.initializeRequestContext(ctx, request);
ctx.correlationId = ctx.headers["X-VULCAIN-CORRELATION-ID"] || guid.v4();
ctx.correlationPath = ctx.headers["X-VULCAIN-CORRELATION-PATH"] || "-";
let tenantPolicy = ctx.container.get(annotations_1.DefaultServiceNames.TenantPolicy);
ctx.tenant = tenantPolicy.resolveTenant(ctx, request);
}
endRequest(begin, response, ctx, e) {
let value = response.value;
let hasError = false;
let prefix;
if (response instanceof response_1.HttpResponse) {
value = response.content;
hasError = response.statusCode && response.statusCode >= 400;
}
if (value) {
hasError = hasError || value.error;
if (value.schema) {
prefix = value.schema.toLowerCase() + "_" + value.action.toLowerCase();
}
else if (value.action) {
prefix = value.action.toLowerCase();
}
else {
value = null; // custom value - don't log it
}
}
else {
hasError = true;
}
const ms = this.calcDelayInMs(begin);
// Duration
prefix && this.metrics.timing(prefix + metrics_1.MetricsConstant.duration, ms);
this.metrics.timing(metrics_1.MetricsConstant.allRequestsDuration, ms);
// Failure
if (hasError) {
prefix && this.metrics.increment(prefix + metrics_1.MetricsConstant.failure);
this.metrics.increment(metrics_1.MetricsConstant.allRequestsFailure);
}
// Always remove userContext
if (value) {
value.userContext = undefined;
}
let trace = {
duration: ms,
info: Object.assign({}, value)
};
// Remove result value for trace
trace.info.value = undefined;
if (e) {
trace.stackTrace = e.stack;
trace.message = e.message;
}
system_1.System.log.write(ctx, trace);
// Normalize return value
if (value) {
value.source = undefined;
value.inputSchema = undefined;
value.startedAt = undefined;
value.completedAt = undefined;
}
}
populateFromQuery(request) {
let params = {};
let count = 0;
;
Object.keys(request.query).forEach(name => {
switch (name) {
case "$action":
case "$schema":
case "$page":
case "$maxByPage":
break;
case "$query":
params = JSON.parse(request.query[name]);
break;
default:
count++;
params[name] = request.query[name];
}
});
return { params, count };
}
populateWithActionSchema(action, request, defaultAction) {
let a;
let s;
if (request.params.schemaAction) {
if (request.params.schemaAction.indexOf('.') >= 0) {
let parts = request.params.schemaAction.split('.');
s = parts[0];
a = parts[1];
}
else {
a = request.params.schemaAction;
}
}
else {
a = request.query.$action;
s = request.query.$schema;
}
action.action = action.action || a || defaultAction;
action.schema = action.schema || s;
}
normalizeCommand(request) {
let action = request.body;
// Body contains only data -> create a new action object
if (!action.action && !action.params && !action.schema) {
action = { params: action };
}
action.domain = this.domainName;
this.populateWithActionSchema(action, request);
action.params = action.params || {};
return action;
}
executeQueryRequest(request, ctx) {
return __awaiter(this, void 0, void 0, function* () {
if (request.user) {
ctx.user = request.user;
}
let query = { domain: this.domainName };
this.populateWithActionSchema(query, request, "all");
if (query.action === "get") {
if (!request.params.id) {
return new response_1.BadRequestResponse("Id is required");
}
let requestArgs = this.populateFromQuery(request);
if (requestArgs.count === 0) {
query.params = request.params.id;
}
else {
query.params = requestArgs.params;
query.params.id = request.params.id;
}
}
else {
query.maxByPage = (request.query.$maxByPage && parseInt(request.query.$maxByPage)) || 100;
query.page = (request.query.$page && parseInt(request.query.$page)) || 0;
query.params = this.populateFromQuery(request).params;
}
return yield this.executeRequest(this.queryManager, query, ctx);
});
}
executeActionRequest(request, ctx, command) {
if (request.user) {
ctx.user = request.user;
}
command = command || this.normalizeCommand(request);
return this.executeRequest(this.commandManager, command, ctx);
}
executeRequest(manager, command, ctx) {
return __awaiter(this, void 0, void 0, function* () {
const begin = this.startRequest(command);
if (!command || !command.domain) {
return new response_1.BadRequestResponse("domain is required.");
}
if (command.domain.toLowerCase() !== this.domainName.toLowerCase()) {
return new response_1.BadRequestResponse("this service doesn't belong to domain " + this.domainName);
}
try {
// Check if handler exists
let info = manager.getInfoHandler(command);
system_1.System.log.info(ctx, `Request context - user=${ctx.user ? ctx.user.name : "<null>"}, scopes=${ctx.user ? ctx.user.scopes : "[]"}, tenant=${ctx.tenant}`);
// Verify authorization
if (!ctx.hasScope(info.metadata.scope)) {
system_1.System.log.info(ctx, `user=${ctx.user ? ctx.user.name : "<null>"}, scopes=${ctx.user ? ctx.user.scopes : "[]"} - Unauthorized for handler scope=${info.metadata.scope} `);
return new response_1.HttpResponse({ error: { message: http.STATUS_CODES[403] } }, 403);
}
// Process handler
let result = yield manager.runAsync(command, ctx);
if (result && command.correlationId) {
result.addHeader("X-VULCAIN-CORRELATION-ID", command.correlationId);
}
// Response
this.endRequest(begin, result, ctx);
return result;
}
catch (e) {
let result = command;
result.error = { message: e.message || e, errors: e.errors };
this.endRequest(begin, result, ctx, e);
return new response_1.HttpResponse(result, e.statusCode);
}
finally {
ctx && ctx.dispose();
}
});
}
}
exports.AbstractAdapter = AbstractAdapter;
//# sourceMappingURL=abstractAdapter.js.map