UNPKG

jumbo-core

Version:

Modern lightweight fast enterprise level MVW framework for Node.js

865 lines 37.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const ErrorResult_1 = require("../results/ErrorResult"); if (Jumbo.config.jumboDebugMode) { console.log("[DEBUG] REQUIRE: Application"); } const $cluster = require("cluster"); const $path = require("path"); const $fs = require("fs"); const $url = require("url"); const $formidable = require("formidable"); const $https = require("https"); const $http = require("http"); const uuid = require("uuid/v1"); const Tokens = require("csrf"); const Exception_1 = require("jumbo-core/exceptions/Exception"); const ViewResult_1 = require("jumbo-core/results/ViewResult"); const Cluster_1 = require("jumbo-core/cluster/Cluster"); const staticFileResolver_1 = require("../base/staticFileResolver"); const $cfg = require("jumbo-core/config-options").Configurations; const USE_HTTPS = Jumbo.config.protocol.protocol === $cfg.Protocols.Https; const CLIENT_MESSAGE_ID = Jumbo.Base.Controller.clientMessagesId; const GLOBALIZATION_ENABLED = Jumbo.config.globalization.enabled; const DEBUG_MODE = Jumbo.config.debugMode; const JUMBO_DEBUG_MODE = Jumbo.config.jumboDebugMode; const LOG_ENABLED = Jumbo.config.log.enabled === true; const DEVELOPMENT_MODE = Jumbo.config.deployment == $cfg.Deployment.Development; const CHECK_INTERVAL_TIME = 5; const TPL_CACHE_EXTENSION = ".tplcache"; const BEFORE_ACTION_NAME = "beforeActions"; let CAN_USE_CACHE = false; const MAX_POST_DATA_SIZE = Jumbo.config.maxPostDataSize; const X_POWERED_BY_ENABLED = !Jumbo.config.security.hidePoweredBy; const X_FRAME_OPTION = Jumbo.config.security.xFrameOptions; const NO_SNIFF = !!Jumbo.config.security.noSniff; exports.CSRF_KEY_NAME = "__forgery_token"; const ONE_DAY_TIME = 24 * 60 * 60; const CSRF_REQUIRING_METHODS = ["POST", "PUT", "DELETE"]; const CSRF_ENABLED = !!Jumbo.config.security.csrf; const istanceKey = Symbol.for("Jumbo.Application.Application"); let instance = global[istanceKey] || null; class Application { constructor() { this.server = null; this.serverIsReady = false; this.port = 80; this.requests = { limit: 0, totalCount: 0, checkInterval: null, limitIP: 0, banTime: 3600, countPerIP: {}, blockedIPs: {} }; this.locator = Locator_1.Locator.instance; this.diContainer = DIContainer_1.DIContainer.instance; this.controllerFactory = ControllerFactory_1.ControllerFactory.instance; this.sessions = {}; this.sessionsSize = 0; this.memoryCache = {}; this.memoryCacheQueue = []; this.memoryCacheSize = 0; this.blockIpListener = null; this.staticFileResolver = staticFileResolver_1.staticFileResolver; this.templateAdapter = null; this.csrf = new Tokens(); this.serverIsRunning = false; if (new.target != ApplicationActivator) { throw new Error("You cannot call private constructor!"); } this.setErrorHandlingEvents(); if ($cluster.isWorker || DEBUG_MODE) { this.createServer(); } this.serverIsReady = true; this.initClustering(); } getLocator() { return this.locator; } getDIContainer() { return this.diContainer; } setStaticFileResolver(handler) { this.staticFileResolver = handler; } setBlockIpListener(listener) { if (typeof listener !== "function") { throw new Error("Listener must be function!"); } this.blockIpListener = listener; } static get instance() { if (instance == null) { global[istanceKey] = instance = Reflect.construct(Application, [], ApplicationActivator); } return instance; } setTemplateAdapter(adapter) { if (adapter.constructor != Object || !("extension" in adapter) || !("preCompilation" in adapter)) { console.error("Adapter you are trying to register is invalid!"); Application.exit(); } this.templateAdapter = adapter; } registerIntervalTask(time, func) { if ($cluster.isMaster) { setInterval(func, time * 1000); } } registerDailyTask(hour, minute, second, func) { throw new Error("Not implemented yet!"); } runWhenReady(port, callback) { this.port = port; let interval = setInterval(() => { if (this.serverIsReady === true) { clearInterval(interval); if (!this.beforeRunWhenReadyCallback()) { Application.exit(); return; } CAN_USE_CACHE = this.templateAdapter.preCompilation && Jumbo.config.cache.enabled; if ($cluster.isMaster) { console.timeEnd("Application Master load-time: "); if (!DEBUG_MODE) { callback(); return; } } this.server.listen(this.port, () => { this.serverIsRunning = true; if (!DEBUG_MODE) { console.timeEnd("Application Worker " + $cluster.worker.id + " load-time: "); Cluster_1.cluster.invoke(Cluster_1.ClusterCommands.WorkerReady); } else { Log_1.Log.line("Server is running on port " + this.port, Log_1.LogTypes.Start, 0); } callback(); }); } }, 50); } getClientIP(request) { return request.headers["X-Forwarded-For"] || request.connection.remoteAddress; } static exit() { if ($cluster.isMaster) { Log_1.Log.line("Exiting application...", Log_1.LogTypes.Std, 0); } else { Log_1.Log.line("Exiting child process...", Log_1.LogTypes.Std, 0); Cluster_1.cluster.invoke(Cluster_1.ClusterCommands.ExitApp); } process.exit(0); } workersReady() { Log_1.Log.line("Server is running on port " + this.port, Log_1.LogTypes.Start, 0); } beforeRunWhenReadyCallback() { if (!this.templateAdapter) { this.setTemplateAdapter(Jumbo.Adapters.TemplateAdapter); } return true; } setErrorHandlingEvents() { process.on("uncaughtException", function (err) { Log_1.Log.error(err.message + "\n" + err.stack); process.exit(1); }).on("unhandledRejection", function (err) { Log_1.Log.error("Unhandled rejection: " + err.message + "\n" + err.stack); process.exit(1); }); } initClustering() { Cluster_1.cluster.initClustering(); } createServer() { this.prepareRequestsSetting(); if (USE_HTTPS) { let options = this.prepareHttpsServerOptions(); this.server = $https.createServer(options, (req, res) => this.serverCallback(req, res)); } else { this.server = $http.createServer((req, res) => this.serverCallback(req, res)); } this.server.on("error", (err) => { if (err !== null) { Log_1.Log.error("Server couldn't be started. Maybe port is blocked.\n" + err.message, Log_1.LogTypes.Std); Application.exit(); } }); } prepareRequestsSetting() { if (Jumbo.config.maxRequestPerSecond !== 0) { this.requests.limit = Jumbo.config.maxRequestPerSecond || this.requests.limit; this.requests.checkInterval = setInterval(() => { this.requests.totalCount = 0; this.requests.countPerIP = {}; }, CHECK_INTERVAL_TIME * 1000); } if (Jumbo.config.DOSPrevention && Jumbo.config.DOSPrevention.enabled === true) { this.requests.limitIP = Jumbo.config.DOSPrevention.maxRequestPerIP || this.requests.limitIP; this.requests.banTime = Jumbo.config.DOSPrevention.blockTime || this.requests.banTime; } else if (Jumbo.config.DOSPrevention && Jumbo.config.DOSPrevention.enabled === false) { this.requests.limitIP = 0; } } prepareHttpsServerOptions() { let options = {}; if (Jumbo.config.protocol.privateKey && Jumbo.config.protocol.certificate) { try { options.key = $fs.readFileSync($path.resolve(Jumbo.BASE_DIR, Jumbo.config.protocol.privateKey)); options.cert = $fs.readFileSync($path.resolve(Jumbo.BASE_DIR, Jumbo.config.protocol.certificate)); } catch (ex) { Log_1.Log.error("Error ocurs while reading private key or certificate. " + ex.message); Application.exit(); } } else if (Jumbo.config.protocol.pfx) { try { options.pfx = $fs.readFileSync($path.resolve(Jumbo.BASE_DIR, Jumbo.config.protocol.pfx)); } catch (ex) { Log_1.Log.error("Error ocurs while reading pkcs archive. " + ex.message); Application.exit(); } } else { Log_1.Log.error("You have not configured HTTPS server properly, key or certificate missing."); Application.exit(); } if (Jumbo.config.protocol.passphrase) { options.passphrase = Jumbo.config.protocol.passphrase; } return options; } async serverCallback(request, response) { let requestBeginTime = new Date().getTime(); this.setResponseHeaders(response); let clientIP = this.getClientIP(request); try { let canContinue = this.checkRequestsLimit(response, clientIP); if (!canContinue) return; canContinue = this.checkIPRequestsLimit(clientIP, response); if (!canContinue) return; canContinue = this.checkEndingDelimiter(request, response); if (!canContinue) return; let aliasOrigUrl; if (!!(aliasOrigUrl = this.locator.getUrlForAlias(request.url))) { request.url = aliasOrigUrl; } canContinue = await this.checkStaticFileRequest(request, response); if (!canContinue) return; await this.processRequest(request, response, requestBeginTime); } catch (ex) { await this.displayError(request, response, ex); } } setResponseHeaders(response) { if (X_POWERED_BY_ENABLED) response.setHeader("X-Powered-By", "JumboJS"); if (X_FRAME_OPTION) response.setHeader("X-Frame-Options", X_FRAME_OPTION); if (NO_SNIFF) response.setHeader("X-Content-Type-Options", "nosniff"); } async checkStaticFileRequest(request, response) { if (request.method == "GET" && request.url.slice(0, 7) === "/public") { let url = decodeURI(request.url); let filePath = $path.join(Jumbo.BASE_DIR, url); if (filePath.slice(0, Jumbo.PUBLIC_DIR.length) != Jumbo.PUBLIC_DIR) { this.plainResponse(response, "Bad Request", 400); return false; } return await new Promise((resolve, reject) => { this.staticFileResolver(filePath, (error, fileStream, mime, size, headers) => { try { if (error) { this.displayError(request, response, { statusCode: 404, message: "File '" + url + "' error. " + error.message }); return resolve(false); } Log_1.Log.line("Streaming static file '" + url + "'.", Log_1.LogTypes.Http, Log_1.LogLevels.Talkative); if (!headers || headers.constructor != Object) { headers = { "Content-Type": mime, "Content-Length": size, "Cache-Control": "public, max-age=43200" }; } response.writeHead(200, headers); fileStream.pipe(response); return resolve(false); } catch (e) { reject(e); } }); }); } return true; } checkEndingDelimiter(request, response) { let urlWithoutEndingDelim; if (request.url.slice(-1) == this.locator.delimiter && (urlWithoutEndingDelim = request.url.replace(Locator_1.END_DELIMITER_TRIM_REGEX, ""))) { Log_1.Log.line("Url ends with delimiter(s), redirecting to url without it.", Log_1.LogTypes.Http, Log_1.LogLevels.Talkative); this.redirectResponse(response, urlWithoutEndingDelim); return false; } return true; } checkIPRequestsLimit(clientIP, response) { if (this.requests.limitIP != 0) { if (this.requests.blockedIPs[clientIP]) { if (this.requests.blockedIPs[clientIP] <= new Date().getTime()) { delete this.requests.blockedIPs[clientIP]; } else { this.plainResponse(response, "We're sorry but server reject your request.", 503); return false; } } if (!this.requests.countPerIP[clientIP]) { this.requests.countPerIP[clientIP] = 1; } else { if (++this.requests.countPerIP[clientIP] > this.requests.limitIP * CHECK_INTERVAL_TIME) { this.requests.blockedIPs[clientIP] = new Date().getTime() + this.requests.banTime * 1000; Log_1.Log.warning("Client from ip " + clientIP + " reached the ip/requests/sec limit and was blocked.", Log_1.LogTypes.Http); if (this.blockIpListener != null) { this.blockIpListener(clientIP); } } } } return true; } checkRequestsLimit(response, clientIP) { if (this.requests.limit != 0) { if (++this.requests.totalCount > this.requests.limit * CHECK_INTERVAL_TIME) { this.plainResponse(response, "We're sorry but server reject your request because of overload.", 503); if (this.requests.totalCount == (this.requests.limit + 1)) { Log_1.Log.warning("Limit of request per second was reached. Last request come from: " + clientIP, Log_1.LogTypes.Http, Log_1.LogLevels.Talkative); } return false; } } return true; } async verifyCsrfToken(jRequest, jResponse, session) { let secret = await this.getCsrfSecret(session); if (CSRF_REQUIRING_METHODS.includes(jRequest.method)) { if (!this.csrf.verify(secret, jRequest.body.fields[exports.CSRF_KEY_NAME])) { this.plainResponse(jResponse.response, "Error 403, forbidden. Invalid token detected. ", 403); return false; } } return true; } getCsrfSecret(session) { return session[exports.CSRF_KEY_NAME]; } async generateCsrfSecret(session) { let secret = await this.csrf.secret(); session[exports.CSRF_KEY_NAME] = secret; return secret; } generateCsrfTokenFor(secret) { return this.csrf.create(secret); } async processRequest(request, response, requestBeginTime) { const jResponse = new Response_1.Response(response); let match = this.locator.parseUrl(request); if (!match || match.constructor != Object) { return this.procUrlParseError(match, request, response, jResponse); } const jRequest = this.buildRequest(request, requestBeginTime, match); let redirectTo = this.checkLongFormatUrl(jRequest, match); if (redirectTo) return jResponse.redirectUrl(redirectTo); this.setClientSession(jRequest, jResponse); jRequest.body = await this.collectBodyData(jRequest, jResponse); let session = await this.getClientSession(jRequest); if (CSRF_ENABLED) { await this.prepareCsrf(session); let csrfValid = await this.verifyCsrfToken(jRequest, jResponse, session); if (!csrfValid) return; } if (DEBUG_MODE) { console.log(`[DEBUG] Request target point Sub-App ${jRequest.subApp}, Controller ${jRequest.controllerFullName}, ` + `Action ${jRequest.actionFullName}, Method ${jRequest.method}`); } let ctrl = await this.createController(jRequest, jResponse, session); let actionResult = await this.callBeforeActions(ctrl, jRequest); if (actionResult !== undefined) await this.afterAction(ctrl, actionResult); if (!response.finished && !ctrl.exited) { actionResult = await this.callAction(ctrl, jRequest); if (actionResult !== undefined) await this.afterAction(ctrl, actionResult); } this.storeSession(ctrl, jRequest); if (LOG_ENABLED) { Log_1.Log.line(request.method.toUpperCase() + " " + (jRequest.noCache ? "no-cached " : "") + request.headers.host + request.url + ` (${jRequest.subApp} subapp)` + ` returned in ${new Date().getTime() - jRequest.beginTime} ms`); } if (ctrl.exited) return; if (!response.finished) { response.end(); } throw new Error(`Action '${jRequest.actionFullName}'` + ` in controller '${jRequest.controllerFullName}' wasn't exited.`); } async prepareCsrf(session) { if (!this.getCsrfSecret(session)) { await this.generateCsrfSecret(session); } } async procUrlParseError(match, request, response, jResponse) { if (match == null || !match.redirectTo) { return this.displayError(request, response, { statusCode: 404, message: `Page not found. Requested URL '${request.url}'` }); } return jResponse.redirectUrl(match.redirectTo, match.statusCode); } setClientSession(jRequest, jResponse) { if (!(jRequest.sessionId = jRequest.getCookie(Jumbo.config.session.sessionsCookieName))) { jRequest.sessionId = uuid(); jResponse.setCookie(Jumbo.config.session.sessionsCookieName, jRequest.sessionId, null, null, "/"); } } async getClientSession(jRequest) { let session = this.sessions[jRequest.sessionId]; if (session != undefined) { return session; } if (Jumbo.config.session.justInMemory === true) { return {}; } return await new Promise(resolve => { $fs.readFile($path.join(Jumbo.SESSION_DIR, jRequest.sessionId + ".session"), "utf-8", (err, sessJson) => { if (!err) { try { resolve(JSON.parse(sessJson) || {}); } catch (e) { } } resolve({}); }); }); } buildRequest(request, requestBeginTime, match) { let target = this.controllerFactory.getTargetPoint(match.subApp, match.controller, match.action, request.method); let req = new Request_1.Request(request); req._bindLocation(match.location, target.subApp, target.controller, target.action, match.params); req.beginTime = requestBeginTime; req.locale = match.locale || this.locator.requestLocaleOrDefault(request); return req; } checkLongFormatUrl(req, match) { if (req.action == Locator_1.DEFAULT_ACTION && match.controllerInUrl) { let url = req.request.url; let query = $url.parse(url).query || ""; if (query) query = "?" + query; if (req.controller == Locator_1.DEFAULT_CONTROLLER) { if (GLOBALIZATION_ENABLED) return "/" + req.locale + query; return "/" + query; } else if (match.actionInUrl) { let delimiter = this.locator.delimiter; if (GLOBALIZATION_ENABLED) return "/" + req.locale + delimiter + req.controller + query; return "/" + req.controller + query; } } return null; } async collectBodyData(req, res) { let end = false; let form = new $formidable.IncomingForm(); form.uploadDir = Jumbo.UPLOAD_DIR; form.keepExtensions = true; return await new Promise((resolve, reject) => { form.on("progress", async () => { try { if (form.bytesExpected > MAX_POST_DATA_SIZE) { end = true; form.emit("error", "The post data received is too big"); await this.displayError(req.request, res.response, { statusCode: 413, message: "The post data received is too big" }); req.request.connection.destroy(); } } catch (e) { reject(e); } }); form.parse(req.request, (err, fields, files) => { if (end) { return; } if (err) { reject(err); } else { resolve({ fields: fields, files: files }); } }); }); } async createController(request, response, session) { let scope = new Jumbo.Ioc.Scope(); let ctrl = this.controllerFactory.createController(this.controllerFactory.getControllerId(request.controller), this.controllerFactory.getSubAppId(request.subApp), scope); this.initController(ctrl, request, response, session, scope); return ctrl; } initController(cntrll, req, res, session, scope) { cntrll._initController(req, res, session, scope); if (req.body != null) { } } async callBeforeActions(ctrl, request) { if (JUMBO_DEBUG_MODE) { console.log("[DEBUG] Application.callBeforeActions() called"); } let beforeActions = ctrl[BEFORE_ACTION_NAME]; if (beforeActions) { let beforeActionsResult = beforeActions(); if (beforeActionsResult.constructor != Promise) { throw new Error(`Action 'beforeActions' in ${ctrl.constructor.name} ` + "is not async method (does not return Promise)."); } let result = await beforeActionsResult; if (result != undefined) return result; } } async callAction(controller, request) { if (JUMBO_DEBUG_MODE) { console.log("[DEBUG] Application.callAction() called"); } let action = controller[request.actionFullName]; if (action) { let controllerId = request.controllerFullName.toLowerCase(); let actionId = request.actionFullName.toLowerCase(); let subAppId = request.subApp.toLowerCase(); let actionParams = this.controllerFactory.getActionInfo(actionId, controllerId, subAppId).params; let args = []; for (let param of actionParams) { args.push(request.params[param] || undefined); } let rtrnVal = action.apply(controller, args); if (rtrnVal.constructor != Promise) { throw new Error(`Action '${request.actionFullName}' in controller ` + request.controllerFullName + " is not async method (does not return Promise)"); } return await rtrnVal; } } async afterAction(controller, actionResult) { if (JUMBO_DEBUG_MODE) { console.log("[DEBUG] Application.afterAction() called"); } let req = controller.request; let res = controller.response; if (actionResult === null) { controller.exited = true; return res.response.end(""); } if (actionResult.constructor === Object) { return Controller_1.Controller.prototype.json.call(controller, actionResult); } if (actionResult.constructor === String) { return Controller_1.Controller.prototype.data.call(controller, actionResult, "text/plain"); } if (actionResult.constructor === ErrorResult_1.ErrorResult || actionResult.constructor === Error || actionResult.constructor === Exception_1.Exception) { controller.exited = true; return this.displayError(req.request, res.response, actionResult); } if (actionResult.constructor == ViewResult_1.ViewResult) { actionResult.data.lang = controller.request.locale.slice(0, 2); actionResult.data._context = actionResult; actionResult.messages = actionResult.data.clientMessages = (controller.crossRequestData[CLIENT_MESSAGE_ID] || {}); actionResult.locale = controller.request.locale; await this.prepareView(controller, req, res, actionResult); if (JUMBO_DEBUG_MODE) { console.log("[DEBUG] Application.afterAction() after prepareView call"); } } else { throw new Error(`Unexpected return value of action '${req.actionFullName}'` + ` in controller '${req.controllerFullName}' or action wasn't exited.`); } } storeSession(cntrll, req) { if (cntrll.session && Object.keys(cntrll.session).length != 0) { this.sessions[req.sessionId] = cntrll.session; if (Jumbo.config.session.justInMemory !== true) { $fs.writeFile($path.join(Jumbo.SESSION_DIR, req.sessionId + ".session"), JSON.stringify(cntrll.session), "utf-8", () => { }); } } } getTemplateCacheName(viewResult, req) { let tplCacheFileName = (req.subApp || "Default") + "-"; if (viewResult.view) { tplCacheFileName += viewResult.view.replace(/[^\w\\\/]/g, "").replace(/[\/\\]/g, "-"); if (viewResult.partialView) { if (viewResult.snippet) { tplCacheFileName += "_snippet-" + viewResult.snippet; } else { tplCacheFileName += "_partial-template"; } } } else { tplCacheFileName += req.controller + "-" + req.action; } tplCacheFileName += TPL_CACHE_EXTENSION; return $path.join(Jumbo.CACHE_DIR, tplCacheFileName); } async prepareView(controller, req, res, viewResult) { if (JUMBO_DEBUG_MODE) { console.log("[DEBUG] Application.prepareView() called"); } if (!viewResult.view) { viewResult.view = req.controller + "/" + req.action; } let tplCacheFile = this.getTemplateCacheName(viewResult, req); if (viewResult.rawTemplate) { let appPath = this.getAppPath(req); let templatePath = this.getTemplatePath(appPath, viewResult); return await new Promise((resolve, reject) => { $fs.readFile(templatePath, "utf-8", (err, content) => { if (err) { return reject(err); } try { this.sendView(content, res, controller); resolve(); } catch (e) { reject(e); } }); }); } if (req.noCache || !CAN_USE_CACHE) { return await this.compileAndRenderView(viewResult, req, res, controller, CAN_USE_CACHE, tplCacheFile); } if (this.memoryCacheQueue.indexOf(tplCacheFile) != -1) { let tpl = await this.templateAdapter.renderPreCompiled(this.memoryCache[tplCacheFile], viewResult.data, controller); return this.sendView(tpl, res, controller); } if (DEBUG_MODE) { console.log("[DEBUG] Reading template from cache file"); } return await new Promise((resolve, reject) => { $fs.readFile(tplCacheFile, "utf-8", async (err, content) => { try { if (err) { await this.compileAndRenderView(viewResult, req, res, controller, true, tplCacheFile); return resolve(); } let tpl = await this.templateAdapter.renderPreCompiled(content, viewResult.data, controller); this.sendView(tpl, res, controller); resolve(); } catch (e) { reject(e); } }); }); } sendView(output, res, ctrl) { if (JUMBO_DEBUG_MODE) { console.log("[DEBUG] Application.sendView() called"); } let response = res.response; res.headers["Content-Length"] = Buffer.byteLength(output, "utf-8"); response.writeHead(200, res.headers); response.end(output); this.afterTemplateRender(ctrl); } async compileAndRenderView(viewResult, req, res, cntrl, writeToCache, tplCacheFileName) { if (JUMBO_DEBUG_MODE) { console.log("[DEBUG] Application.renderView() called"); } let { templatePath, layoutPath, dynamicLayout } = this.prepareRenderViewProperties(req, viewResult); if (writeToCache) { if (DEBUG_MODE) { console.log("[DEBUG] Precompiling template"); } let compiledtemplate = await this.templateAdapter.preCompile(templatePath, layoutPath, dynamicLayout); this.cacheViewTemplate(tplCacheFileName, compiledtemplate); if (DEBUG_MODE) { console.log("[DEBUG] Rendering template"); } let tpl = await this.templateAdapter.renderPreCompiled(compiledtemplate, viewResult.data, cntrl); this.sendView(tpl, res, cntrl); } else { if (DEBUG_MODE) { console.log("[DEBUG] Complete (compile and) render"); } let tpl = await this.templateAdapter.render(templatePath, layoutPath, dynamicLayout, viewResult.data, cntrl); this.sendView(tpl, res, cntrl); } } prepareRenderViewProperties(req, viewResult) { let appPath = this.getAppPath(req); let templatePath = this.getTemplatePath(appPath, viewResult); let layoutPath = null; let dynamicLayout = null; if (viewResult.snippet) { dynamicLayout = '{include ' + viewResult.snippet + '}'; if (DEBUG_MODE) { console.log("[DEBUG] Rendering snippet ", viewResult.snippet, "of", templatePath); } } else if (viewResult.partialView) { if (DEBUG_MODE) { console.log("[DEBUG] Rendering partial template ", templatePath); } } else { layoutPath = $path.join(appPath, "templates", "layout" + this.templateAdapter.extension); if (DEBUG_MODE) { console.log("[DEBUG] Rendering template '", templatePath, "' with layout", layoutPath); } } return { templatePath, layoutPath, dynamicLayout }; } getTemplatePath(appPath, viewResult) { return $path.join(appPath, "templates", viewResult.view + this.templateAdapter.extension); } getAppPath(req) { if (req.subApp == ControllerFactory_1.MAIN_SUBAPP_NAME) { return Jumbo.APP_DIR; } return $path.join(Jumbo.APP_DIR, "sub-apps", this.controllerFactory.getSubAppInfo(req.subApp).dir); } cacheViewTemplate(tplCacheFile, compiledtemplate) { if (this.memoryCache[tplCacheFile]) { this.memoryCacheSize -= this.memoryCache[tplCacheFile].length; delete this.memoryCache[tplCacheFile]; } let size = compiledtemplate.length; if (size < Jumbo.config.cache.memoryCacheSizeLimit) { while (this.memoryCacheSize + size > Jumbo.config.cache.memoryCacheSizeLimit) { let item = this.memoryCacheQueue.shift(); this.memoryCacheSize -= this.memoryCache[item].length; delete this.memoryCache[item]; } this.memoryCache[tplCacheFile] = compiledtemplate; this.memoryCacheSize += size; this.memoryCacheQueue.push(tplCacheFile); } $fs.writeFile(tplCacheFile, compiledtemplate, () => { }); } afterTemplateRender(ctrl) { ctrl._clearOldCrossRequestData(); ctrl.exit(); } plainResponse(response, message, code) { response.writeHead(code, { "Content-Type": "text/plain", "Content-Length": Buffer.byteLength(message, "utf-8") }); response.end(message); } redirectResponse(response, url, statusCode = 302) { response.writeHead(statusCode, { "Location": url }); response.end(); } async displayError(request, response, errObj) { let ex = errObj.error || errObj; let errorObj = { message: errObj.message || ex.message, status: ex.statusCode || errObj.status || 500, stack: ex.stack }; Log_1.Log.warning("Error while serving " + request.url + "; Client " + this.getClientIP(request) + "; " + errorObj.stack, Log_1.LogTypes.Http); if (response.finished) return; if (DEVELOPMENT_MODE && (ex instanceof Error || ex instanceof Exception_1.Exception)) { return this.renderException(errorObj.message, ex, errorObj.status, request, response); } let errFile = $path.resolve(Jumbo.ERR_DIR, errorObj.status + ".html"); let errExists = await new Promise(resolve => { $fs.access(errFile, $fs.constants.R_OK, err => { resolve(!err); }); }); if (errExists) { this.staticFileResolver(errFile, (error, fileStream, mime, size) => { if (error) { Log_1.Log.error("Applicaton.displayError(): " + error.message); return this.plainResponse(response, "Internal server error.", 500); } response.writeHead(errorObj.status, { "Content-Type": "text/html", "Content-Length": size }); fileStream.pipe(response); }); } else { try { this.plainResponse(response, errorObj.status, "Status code " + errorObj.status + "\nWe're sorry but some error occurs."); } catch (ex) { Log_1.Log.error(ex.message, Log_1.LogTypes.Std); } } } renderException(message, ex, status, request, response) { let result = require("jumbo-core/exception-template.js")(message, ex, status, request); response.writeHead(status, { "Content-Type": "text/html", "Content-Length": Buffer.byteLength(result, "utf-8") }); response.end(result); } } exports.Application = Application; class ApplicationActivator extends Application { } const Locator_1 = require("jumbo-core/application/Locator"); const ControllerFactory_1 = require("jumbo-core/application/ControllerFactory"); const DIContainer_1 = require("jumbo-core/ioc/DIContainer"); const Log_1 = require("jumbo-core/logging/Log"); const Response_1 = require("jumbo-core/application/Response"); const Request_1 = require("jumbo-core/application/Request"); const Controller_1 = require("jumbo-core/base/Controller"); if (Jumbo.config.jumboDebugMode) { console.log("[DEBUG] REQUIRE: Application END"); } //# sourceMappingURL=Application.js.map