vmagic
Version:
vMagic is a RESTFul framework for NodeJS applications.
323 lines (277 loc) • 11.4 kB
JavaScript
/*eslint max-statements: ["error", 100], max-lines: ["error", 500]*/
;
import { parse } from "url";
import FactoryController from "../Controller/FactoryController.js";
import Logger from "../Component/Logger.js";
import pkg from "../../package.json" assert {type: "json"};
const logger = new Logger();
class RequestHandler {
constructor(application) {
this.application = application;
this.factoryController = new FactoryController(this.application);
}
async init() {
await this.factoryController.init();
await this.loadCoreConfig();
}
async loadCoreConfig() {
const coreConfigPath = `${this.application.configPath}/core.json`;
const coreConfigModule = await import(coreConfigPath, { assert: { type: 'json' } });
this.core = coreConfigModule.default; // O JSON será importado como um objeto
}
/**
* [getPayload description]
* @param {[type]} request [description]
* @param {Function} callback [description]
* @return {[type]} [description]
*/
getPayload(request) {
return new Promise((resolve, reject) => {
const contentType = request.headers["content-type"];
if (contentType && contentType.indexOf("multipart/form-data") > -1) {
resolve({});
} else {
let body = "";
request.on("data", data => {
body += data;
//Too much POST data, kill the connection!
if (body.length > 1e6) {
request.connection.destroy();
reject();
}
});
request.on("end", () => {
let payload = null;
if (body !== "") {
try {
payload = JSON.parse(body);
} catch (err) {
logger.error("[Magic] Could not convert payload to JSON Object.");
payload = body;
}
}
logger.info(`[Magic] Payload ${JSON.stringify(payload, null, 4)}`);
resolve(payload);
});
}
});
}
/**
* Convert the query string to JSON Object.
* @param {Object} request - Object request.
* @return {Object} Returns the query string.
*/
getQuery(request) {
const query = parse(request.url, true).query;
logger.info(`[Magic] QueryString ${JSON.stringify(query, null, 4)}`);
return query;
}
/**
* Its returns an object with url parts.
* @param {Object} request - Http request.
* @return {Object} result
*/
getParts(request) {
let index = null;
let params = null;
index = request.url.indexOf("?");
if (index > -1) {
params = request.url.substring(0, index).split("/");
} else {
params = request.url.split("/");
}
return [params[1], params[2], params[3]];
}
/**
* [terminate description]
* @param {[type]} request [description]
* @param {[type]} response [description]
* @param {[type]} data [description]
* @param {[type]} statusCode [description]
* @return {[type]} [description]
*/
terminate(request, response, data, statusCode = 200) {
this.request = request;
this.response = response;
this.data = data;
if (statusCode) {
this.response.writeHead(statusCode, { "Content-Type": "application/json" });
} else {
this.response.writeHead(200, { "Content-Type": "application/json" });
}
this.response.end(JSON.stringify(this.data));
this.request.connection.destroy();
}
responseClient(response, request, controller, result) {
const responseHeader = controller.responseHeader || { "Content-Type": "application/json; charset=utf8" };
response.writeHead(controller.statusCode, responseHeader);
request.pipe(response);
response.end(controller.responseHeader ? result : JSON.stringify(result));
}
/**
* This function is called whenever http request is received.
* @param {Object} request .
* @param {Object} response .
* @return {VoidFunction} .
*/
async process(request, response) {
if (!Reflect.has(this.core, "cors")) {
//Website you wish to allow to connect.
response.setHeader("Access-Control-Allow-Origin", "*");
//Request method you wish to allow.
response.setHeader("Access-Control-Request-Method", "*");
//Request methods you wish to allow
response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, PATCH");
//Request headers you wish to allow
response.setHeader("Access-Control-Allow-Headers", "*");
//Set to true if you need the website to include cookies in the requests sent to the API (e.g. in case you use sessions)
response.setHeader("Access-Control-Allow-Credentials", true);
} else {
const origin = request.headers.origin;
for (const key in this.core.cors) {
if (key === "Access-Control-Allow-Origin") {
const allowedOrigins = this.core.cors[key];
if (Array.isArray(allowedOrigins) && allowedOrigins.includes(origin)) {
response.setHeader(key, origin);
} else if (typeof allowedOrigins === "string") {
response.setHeader(key, allowedOrigins);
}
} else {
response.setHeader(key, this.core.cors[key]);
}
}
}
const headersInfo = request.headers;
headersInfo.url = request.url;
headersInfo.method = request.method;
headersInfo.statusCode = request.statusCode;
logger.info(`[Magic] Request Data ${JSON.stringify(headersInfo, null, "\t")}`);
const urlParts = this.getParts(request);
const resource = urlParts[0];
let controller = null;
let action = null;
let id = null;
try {
let result = null;
if (!resource && !urlParts[1]) {
logger.warn(`[Magic] Version ${pkg.version}`);
this.terminate(request, response, { name: "vMagic", version: pkg.version });
return;
}
//Get controller instance.
controller = await this.factoryController.controller(resource);
controller.headersInfo = headersInfo;
//It must validate if exists action.
const hasNumber = new RegExp("\\d", "gi");
if (!hasNumber.test(urlParts[1]) && urlParts[1] && typeof controller[urlParts[1]] === "undefined") {
controller.statusCode = 404;
throw new Error(`Action /${resource}/${urlParts[1]} not found.`);
}
//It must separate what is action and what is id.
if (typeof controller[urlParts[1]] !== "undefined") {
action = urlParts[1];
id = urlParts[2];
} else {
id = urlParts[1];
}
if (typeof controller.beforeFilter !== "undefined") {
await controller.beforeFilter();
}
if (typeof controller.init !== "undefined") {
await controller.init();
}
//Set status code whether is not exists.
if (typeof controller.statusCode === "undefined" || controller.statusCode === "") {
controller.statusCode = 200;
}
/*
* If the http request method is GET
*/
if (request.method === "GET") {
controller.query = this.getQuery(request);
controller.id = id || null;
if (action) {
result = await controller[action](request, response);
} else {
result = await controller.get(request, response);
}
if (result) {
this.responseClient(response, request, controller, result);
}
}
/*
* If the http request method is POST
*/
if (request.method === "POST") {
controller.payload = await this.getPayload(request);
controller.id = id || null;
if (action) {
result = await controller[action](request, response);
} else {
result = await controller.post(request, response);
}
if (result) {
this.responseClient(response, request, controller, result);
}
}
/*
* If the http request method is PUT
*/
if (request.method === "PUT") {
controller.payload = await this.getPayload(request);
controller.id = id || null;
if (action) {
result = await controller[action](request, response);
} else {
result = await controller.put(request, response);
}
if (result) {
this.responseClient(response, request, controller, result);
}
}
/*
* If the http request method is DELETE
*/
if (request.method === "DELETE") {
controller.payload = await this.getPayload(request);
controller.id = id || null;
if (action) {
result = await controller[action](request, response);
} else {
result = await controller.delete(request, response);
}
if (result) {
this.responseClient(response, request, controller, result);
}
}
/*
* If the http request method is OPTIONS
*/
if (request.method === "OPTIONS") {
response.writeHead(200);
response.end();
}
} catch (err) {
const objError = {
error: {
title: "Request Failed",
},
};
if (err.message === "RESOURCE_NOT_FOUND") {
objError.error.statusCode = 400;
objError.error.detail = `Resource /${resource} not found.`;
this.terminate(request, response, objError, 400);
} else {
if (controller) {
objError.error.statusCode = controller.statusCode;
} else {
objError.error.statusCode = 500;
}
objError.error.detail = err.message;
this.terminate(request, response, objError, objError.error.statusCode);
}
logger.error(objError);
}
}
}
export default RequestHandler;