"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.HttpParser = void 0;
const path_1 = __importDefault(require("path"));
const fs_1 = __importDefault(require("fs"));
const os_1 = __importDefault(require("os"));
const handlebars_1 = __importDefault(require("handlebars"));
const logger_1 = __importDefault(require("../logger"));
let DELAY = 0;
/**
* Create a parser class which defines methods to parse
* 1. Request URL to get a matching directory
* 2. From matched directory get .mock file content and generate a response
*/
class HttpParser {
/**
*
* @param {express.Request} req Express Request object for current instance of incoming request
* @param {express.Response} res Express response to be sent to client
* @param {string} mockDir location of http mocks
*/
constructor(req, res, mockDir) {
/**
* Finds a closest match dir for an incoming request
* @returns {string} matchedDir for a given incoming request
*/
this.getMatchedDir = () => {
const reqDetails = {
method: this.req.method.toUpperCase(),
path: this.req.path,
protocol: this.req.protocol,
httpVersion: this.req.httpVersion,
query: this.req.query,
headers: this.req.headers,
body: this.req.body,
};
const matchedDir = getWildcardPath(reqDetails.path, this.mockDir);
return matchedDir;
};
/**
* Defines a default response, if the closest matchedDir is present, parses and sends the response from mockfile,
* Looks for API Level, and Global level default response overrides if mockfile is not found.
* If no default response overrides are found, send the defined default response
* @param {string} mockFile location of of the closest mached mockfile for incoming request
* @returns {string} matchedDir for a given incoming request
*/
this.getResponse = (mockFile) => {
// Default response
let response = {
status: 404,
body: '{"error": "Not Found"}',
headers: {
"content-type": "application/json",
},
};
// Check if mock file exists
if (fs_1.default.existsSync(mockFile)) {
this.prepareResponse(mockFile);
}
else {
logger_1.default.error(`No suitable mock file found: ${mockFile}`);
if (fs_1.default.existsSync(path_1.default.join(this.mockDir, "__", "GET.mock"))) {
logger_1.default.debug(`Found a custom global override for default response. Sending custom default response.`);
this.prepareResponse(path_1.default.join(this.mockDir, "__", "GET.mock"));
}
else {
//If no mockFile is found, return default response
logger_1.default.debug(`No custom global override for default response. Sending default Camouflage response.`);
this.res.statusCode = response.status;
let headerKeys = Object.keys(response.headers);
headerKeys.forEach((headerKey) => {
// @ts-ignore
res.setHeader(headerKey, response.headers[headerKey]);
});
this.res.send(response.body);
}
}
};
/**
* - Since response file contains headers and body both, a PARSE_BODY flag is required to tell the logic if it's currently parsing headers or body
* - Set responseBody to an empty string and set a default response object
* - Set default response
* - Compile the handlebars used in the contents of mockFile
* - Generate actual response i.e. replace handlebars with their actual values and split the content into lines
* - If the mockfile contains the delimiter ====, split the content using the delimiter and pick one of the responses at random
* - Split file contents by os.EOL and read file line by line
* - Set PARSE_BODY flag to try when reader finds a blank line, since according to standard format of a raw HTTP Response, headers and body are separated by a blank line.
* - If line includes HTTP/HTTPS i.e. first line. Get the response status code
* - If following conditions are met:
* - Line is not blank; and
* - Parser is not currently parsing response body yet i.e. PARSE_BODY === false
* - Then:
* - Split line by :, of which first part will be header key and 2nd part will be header value
* - If headerKey is response delay, set variable DELAY to headerValue
* - If parsing response body, i.e. PARSE_BODY === true. Concatenate every line till last line to a responseBody variable
* - If on last line of response, do following:
* - Trim and remove whitespaces from the responseBody
* - Compile the Handlebars to generate a final response
* - Set PARSE_BODY flag back to false and responseBody to blank
* - Set express.Response Status code to response.status
* - Send the generated Response, from a timeout set to send the response after a DELAY value
* @param {string} mockFile location of of the closest mached mockfile for incoming request
*/
this.prepareResponse = (mockFile) => {
let PARSE_BODY = false;
let responseBody = "";
let response = {
status: 404,
body: '{"error": "Not Found"}',
headers: {
"content-type": "application/json",
},
};
const template = handlebars_1.default.compile(fs_1.default.readFileSync(mockFile).toString());
let fileResponse = template({ request: this.req, logger: logger_1.default });
if (fileResponse.includes("====")) {
const fileContentArray = removeBlanks(fileResponse.split("===="));
fileResponse = fileContentArray[Math.floor(Math.random() * fileContentArray.length)];
}
const fileContent = fileResponse.trim().split(os_1.default.EOL);
fileContent.forEach((line, index) => {
if (line === "") {
PARSE_BODY = true;
}
if (line.includes("HTTP")) {
const regex = /(?<=HTTP\/\d).*?\s+(\d{3,3})/i;
if (!regex.test(line)) {
logger_1.default.error("Response code should be valid string");
throw new Error("Response code should be valid string");
}
response.status = line.match(regex)[1];
logger_1.default.debug("Response Status set to " + response.status);
}
else {
if (line !== "" && !PARSE_BODY) {
let headerKey = line.split(":")[0];
let headerValue = line.split(":")[1];
if (headerKey === "Response-Delay") {
DELAY = headerValue;
logger_1.default.debug(`Delay Set ${headerValue}`);
}
else {
this.res.setHeader(headerKey, headerValue);
logger_1.default.debug(`Headers Set ${headerKey}: ${headerValue}`);
}
}
}
if (PARSE_BODY) {
responseBody = responseBody + line;
}
if (index == fileContent.length - 1) {
this.res.statusCode = response.status;
if (responseBody.includes("camouflage_file_helper")) {
const fileResponse = responseBody.split(";")[1];
setTimeout(() => {
this.res.sendFile(fileResponse);
}, DELAY);
}
else {
responseBody = responseBody.replace(/\s+/g, " ").trim();
responseBody = responseBody.replace(/{{{/, "{ {{");
responseBody = responseBody.replace(/}}}/, "}} }");
const template = handlebars_1.default.compile(responseBody);
try {
const codeResponse = JSON.parse(responseBody);
switch (codeResponse["CamouflageResponseType"]) {
case "code":
this.res.statusCode = codeResponse["status"] || this.res.statusCode;
if (codeResponse["headers"]) {
Object.keys(codeResponse["headers"]).forEach((header) => {
this.res.setHeader(header, codeResponse["headers"][header]);
});
}
setTimeout(() => {
logger_1.default.debug(`Generated Response ${codeResponse["body"]}`);
this.res.send(codeResponse["body"]);
});
break;
default:
setTimeout(() => {
logger_1.default.debug(`Generated Response ${template({ request: this.req, logger: logger_1.default })}`);
this.res.send(template({ request: this.req, logger: logger_1.default }));
}, DELAY);
break;
}
}
catch (error) {
logger_1.default.warn(error.message);
setTimeout(() => {
logger_1.default.debug(`Generated Response ${template({ request: this.req, logger: logger_1.default })}`);
this.res.send(template({ request: this.req, logger: logger_1.default }));
}, DELAY);
}
}
PARSE_BODY = false;
responseBody = "";
DELAY = 0;
}
});
};
this.req = req;
this.mockDir = mockDir;
this.res = res;
}
}
exports.HttpParser = HttpParser;
const removeBlanks = (array) => {
return array.filter(function (i) {
return i;
});
};
const getWildcardPath = (dir, mockDir) => {
let steps = removeBlanks(dir.split("/"));
let testPath;
let newPath = path_1.default.resolve(mockDir);
while (steps.length) {
let next = steps.shift();
testPath = path_1.default.join(newPath, next);
if (fs_1.default.existsSync(testPath)) {
newPath = testPath;
testPath = path_1.default.join(newPath, next);
}
else {
testPath = path_1.default.join(newPath, "__");
if (fs_1.default.existsSync(testPath)) {
newPath = testPath;
continue;
}
else {
newPath = testPath;
break;
}
}
}
return newPath;
};
//# sourceMappingURL=HttpParser.js.map