UNPKG

19.6 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3var path = require("path");
4var fs = require("fs");
5var Http = require("http");
6var express = require("express");
7var Utils_1 = require("./Utils");
8var winston = require("winston");
9var helmet = require("helmet");
10var morgan = require("morgan");
11var cookieParser = require("cookie-parser");
12var bodyParser = require("body-parser");
13var serveFavicon = require("serve-favicon");
14var Session = require("express-session");
15var FileStore = require("session-file-store");
16var md5 = require("md5");
17var User_1 = require("./User");
18var glob = require("glob");
19var util = require("util");
20var pgPromise = require("pg-promise");
21require("winston-daily-rotate-file");
22var HttpApplication = (function () {
23 function HttpApplication(appName, rootFolder, options) {
24 this.getAcceptedLangugeHeader = function (req) {
25 var data = req.headers["accept-language"];
26 if (data) {
27 var lst = data.split(",");
28 if (lst.length !== 0) {
29 return lst[0];
30 }
31 else {
32 data;
33 }
34 }
35 else {
36 return null;
37 }
38 };
39 var me = this, viewPath = path.join(__dirname, "..", "resources", "views", "error");
40 me.isTestMode = false;
41 me.httpErrorViews = {};
42 me.setHttpErrorView(404, path.join(viewPath, "404.pug"));
43 me.setHttpErrorView(500, path.join(viewPath, "500.pug"));
44 me.options = options || {};
45 me.rootFolder = rootFolder;
46 me.appName = appName;
47 me.expressApp = express();
48 me.expressApp.set(HttpApplication.KEY_APPLICATION_NAME, appName);
49 me.expressApp.set(HttpApplication.KEY_ROOT_FOLDER, rootFolder);
50 me.normalizeOptions();
51 me.createRuntimeFolders();
52 me.createLogger();
53 }
54 HttpApplication.prototype.enterTestMode = function () {
55 var me = this;
56 me.isTestMode = true;
57 };
58 HttpApplication.prototype.setHttpErrorView = function (httpStatus, viewFullPath) {
59 var me = this;
60 if (fs.existsSync(viewFullPath)) {
61 me.httpErrorViews[httpStatus] = viewFullPath;
62 }
63 else {
64 throw new Error("Could not resolve " + viewFullPath);
65 }
66 };
67 HttpApplication.prototype.createLogger = function () {
68 var me = this, logFolder = Utils_1.Utils.ensureFolder(path.join(me.varFolder, "log"));
69 me.logger = new winston.Logger({ exitOnError: false });
70 me.logger.configure({
71 transports: [
72 new (winston.transports.File)({
73 name: "error-log",
74 level: "error",
75 maxsize: me.options.maxLogSize,
76 filename: path.join(logFolder, "error.log")
77 }),
78 new (winston.transports.DailyRotateFile)({
79 name: "daily-log",
80 maxsize: me.options.maxLogSize,
81 filename: path.join(logFolder, "application-"),
82 datePattern: "yyyy-MM.log",
83 level: me.options.appLogLevel
84 })
85 ]
86 });
87 me.logFolder = logFolder;
88 me.expressApp.set("_logger", me.logger);
89 me.logger.info("%s loggers initialized with [%s] level", me.appName, me.options.appLogLevel.toUpperCase());
90 };
91 HttpApplication.prototype.normalizeOptions = function () {
92 var me = this;
93 me.options.port = me.options.port || 8090;
94 me.options.address = me.options.address || "127.0.0.1";
95 me.options.appLogLevel = me.options.appLogLevel || (me.isDevel() ? "debug" : me.getEnv("loglevel", "info"));
96 me.options.maxLogSize = me.options.maxLogSize || (10 * (1024 * 1024));
97 me.options.runtimeFolder = me.options.runtimeFolder || path.join(me.rootFolder, "var");
98 };
99 HttpApplication.prototype.isDevel = function () {
100 var me = this;
101 return me.getEnv("env", "development") !== "production";
102 };
103 HttpApplication.prototype.getEnv = function (key, defValue) {
104 var me = this, key = (me.appName + "_" + key);
105 return (process.env[key.toLowerCase()] || process.env[key.toUpperCase()] || defValue);
106 };
107 HttpApplication.prototype.configureHelmetSecurity = function (options) {
108 var me = this;
109 me.logger.info("Configuring Helmet");
110 me.expressApp.use(helmet(options));
111 me.expressApp.use(helmet.hidePoweredBy({ setTo: "PHP 4.2.0" }));
112 return true;
113 };
114 HttpApplication.prototype.configureHttpLogger = function () {
115 var me = this, loggerName = me.createHttpLogger(), httpLogger = winston.loggers.get(loggerName), jsonCombined = function (tokens, req, res) {
116 return JSON.stringify({
117 remote_user: tokens["remote-user"](req, res) || "anonymus",
118 remote_addr: tokens["remote-addr"](req),
119 method: tokens["method"](req, res),
120 status: tokens["status"](req, res),
121 timestamp: tokens["date"](req, res, "iso"),
122 url: tokens["url"](req, res),
123 request_headers: req.headers,
124 response_headers: res._headers,
125 response_time: tokens["response-time"](req, res)
126 });
127 };
128 var args = [
129 jsonCombined,
130 {
131 stream: {
132 write: function (message) {
133 httpLogger.info(message.trim());
134 }
135 }
136 }
137 ];
138 me.expressApp.use(morgan.apply(morgan, args));
139 return true;
140 };
141 HttpApplication.prototype.createHttpLogger = function () {
142 var me = this, loggerName = "http-logger";
143 winston.loggers.add(loggerName, {
144 transports: [
145 new (winston.transports.DailyRotateFile)({
146 name: "http-log",
147 filename: path.join(me.logFolder, "access-"),
148 datePattern: "yyyy-MM.log",
149 level: me.options.appLogLevel,
150 json: false,
151 maxsize: me.options.maxLogSize,
152 formatter: function (options) {
153 return options["message"];
154 }
155 })
156 ]
157 });
158 return loggerName;
159 };
160 HttpApplication.prototype.configurePostgreSQLDatabase = function (host, database, username, password, port, options) {
161 var me = this, constr = util.format("postgres://%s:%s@%s:%s/%s", username, password, host, port || 5432, database);
162 me.pgp = pgPromise(options || {});
163 me.database = me.pgp(constr);
164 };
165 HttpApplication.prototype.configureContentParsers = function () {
166 var me = this;
167 me.expressApp.use(cookieParser());
168 me.expressApp.use(bodyParser.json());
169 me.expressApp.use(bodyParser.urlencoded({ extended: false }));
170 return true;
171 };
172 HttpApplication.prototype.configureFilebasedSessionContainer = function () {
173 var me = this, sessionFolder = Utils_1.Utils.ensureFolder(path.join(me.varFolder, ".session")), fileStoreOptions = {
174 path: sessionFolder,
175 useAsync: true,
176 reapInterval: 5000,
177 maxAge: 10000,
178 }, sessionOptions = {
179 secret: md5(me.rootFolder),
180 name: md5(me.rootFolder),
181 cookie: {}
182 };
183 if (!me.isDevel()) {
184 me.expressApp.set("trust proxy", 1);
185 if (!me.isTestMode) {
186 sessionOptions.cookie["secure"] = true;
187 }
188 }
189 sessionOptions.store = new (FileStore(Session))(fileStoreOptions);
190 sessionOptions.resave = true;
191 sessionOptions.saveUninitialized = true;
192 me.expressApp.use(Session(sessionOptions));
193 return true;
194 };
195 HttpApplication.prototype.configureSessionContainer = function () {
196 var me = this;
197 return me.configureFilebasedSessionContainer();
198 };
199 HttpApplication.prototype.getCurrentUser = function (req) {
200 return req.session[HttpApplication.KEY_AUTH_USER] || null;
201 };
202 HttpApplication.prototype.setCurrentUser = function (req, user) {
203 req.session[HttpApplication.KEY_AUTH_USER] = user;
204 };
205 HttpApplication.prototype.getAvailableLocales = function (req) {
206 return req.app.get(HttpApplication.KEY_AVAILABLE_LOCALES) || [];
207 };
208 HttpApplication.prototype.getCurrentLocale = function (req) {
209 var me = this;
210 return req.session[HttpApplication.KEY_LOCALE] || me.getAcceptedLangugeHeader(req) || "en";
211 };
212 HttpApplication.prototype.getContentType = function (req) {
213 return req.headers["content-type"] || null;
214 };
215 HttpApplication.prototype.configureAuthenticatedUser = function () {
216 var me = this;
217 me.expressApp.use(function (req, res, next) {
218 if (!me.getCurrentUser(req)) {
219 req.session[HttpApplication.KEY_AUTH_USER] = new User_1.AnonymousUser();
220 }
221 next();
222 });
223 return true;
224 };
225 HttpApplication.prototype.configureFileBasedTranslationProvider = function (folders) {
226 var me = this, tokens = {
227 "${rootFolder}": me.rootFolder,
228 "${thisFolder}": null
229 }, file = null, db = {};
230 var parseValue = function (value, sourceDir) {
231 tokens["${thisFolder}"] = sourceDir;
232 for (var k in tokens) {
233 if (value.indexOf(k) === 0) {
234 debugger;
235 file = fs.realpathSync(value.replace(k, tokens[k]));
236 value = fs.readFileSync(file).toLocaleString();
237 }
238 }
239 return value;
240 };
241 var procesData = function (data, file) {
242 for (var msg in data) {
243 for (var loc in data[msg]) {
244 if (!db[loc]) {
245 db[loc] = {};
246 }
247 db[loc][msg] = parseValue(data[msg][loc], path.dirname(file));
248 }
249 }
250 };
251 folders.forEach(function (item) {
252 glob.sync(item).forEach(function (file) {
253 try {
254 procesData(JSON.parse(fs.readFileSync(file).toLocaleString()), file);
255 }
256 catch (e) {
257 me.logger.error(e.message);
258 }
259 });
260 });
261 me.expressApp.set(HttpApplication.KEY_I18N, db);
262 };
263 HttpApplication.prototype.translate = function (req, message) {
264 var args = [];
265 for (var _i = 2; _i < arguments.length; _i++) {
266 args[_i - 2] = arguments[_i];
267 }
268 return util.format.apply(util.format, [((req.app.get(HttpApplication.KEY_I18N) || {})[this.getCurrentLocale(req)] || {})[message] || message].concat(args));
269 };
270 HttpApplication.prototype.configureLocaleProvider = function (parameterName, defaultLocale, availableLocales) {
271 var me = this;
272 me.expressApp.use(function (req, res, next) {
273 var accept_language = me.getAcceptedLangugeHeader(req);
274 defaultLocale = defaultLocale || accept_language || "en";
275 availableLocales = availableLocales || [defaultLocale];
276 if (availableLocales.indexOf(defaultLocale) === -1) {
277 availableLocales.push(defaultLocale);
278 }
279 var get_param = req.query[parameterName] || null;
280 var post_param = req.body[parameterName] || null;
281 var sess_param = me.getCurrentLocale(req) || null;
282 var value = get_param || post_param || sess_param || accept_language || defaultLocale;
283 value = availableLocales.indexOf(value) !== -1 ? value : defaultLocale;
284 req.session[HttpApplication.KEY_LOCALE] = value;
285 if (!me.getAvailableLocales(req)) {
286 req.app.set(HttpApplication.KEY_AVAILABLE_LOCALES, availableLocales);
287 }
288 next();
289 });
290 return true;
291 };
292 HttpApplication.prototype.configureStaticFolderProvider = function (folders) {
293 var me = this;
294 Object.keys(folders).forEach(function (mapping) {
295 me.logger.info("Serving %s as %s", folders[mapping], mapping);
296 if (mapping === "/") {
297 me.expressApp.use(express.static(folders[mapping], {
298 fallthrough: true
299 }));
300 }
301 else {
302 me.expressApp.use("/" + mapping, express.static(folders[mapping], {
303 fallthrough: false
304 }));
305 }
306 });
307 return true;
308 };
309 HttpApplication.prototype.configureFaciconProvider = function (faviconPath) {
310 var me = this;
311 me.expressApp.use(serveFavicon(faviconPath));
312 };
313 HttpApplication.prototype.configurePugTemplatesProvider = function (pugOptions) {
314 var me = this;
315 me.expressApp.set("view engine", "pug");
316 me.expressApp.set("view options", pugOptions || {});
317 };
318 HttpApplication.prototype.configureApplicationInternal = function () {
319 var me = this, ok = false;
320 if (me.configureHelmetSecurity()) {
321 if (me.configureHttpLogger()) {
322 if (me.configureContentParsers()) {
323 if (me.configureSessionContainer()) {
324 if (me.configureAuthenticatedUser()) {
325 ok = true;
326 }
327 }
328 }
329 }
330 }
331 return ok;
332 };
333 HttpApplication.prototype.configure404StatusProvider = function () {
334 var me = this;
335 me.expressApp.use(function (req, res, next) {
336 var err = new Error(req.originalUrl + " Not Found!");
337 err.status = 404;
338 next(err);
339 });
340 };
341 HttpApplication.prototype.handleFinalJSONError = function (err, req, res, next) {
342 var me = this;
343 res.json({
344 error: true,
345 message: err.mesage,
346 status: err.status,
347 stack: me.isDevel() ? err.stack : null
348 });
349 };
350 HttpApplication.prototype.handleFinalHTMLError = function (err, req, res, next) {
351 var me = this, errorViewFile = me.httpErrorViews[err.status];
352 fs.exists(errorViewFile, function (exists) {
353 if (exists) {
354 res.render(errorViewFile, {
355 appName: me.appName,
356 err: err,
357 locale: me.getCurrentLocale(req) || "en"
358 });
359 }
360 else {
361 res.send("Error: " + err.status + "\nMessage: " + err.message + "\nStack: " + (me.isDevel() ? err.stack : ""));
362 }
363 });
364 };
365 HttpApplication.prototype.configureFinalErrorProvider = function () {
366 var me = this;
367 me.expressApp.use(function (err, req, res, next) {
368 me.logger.error("Error", {
369 status: err.status,
370 message: err.message,
371 url: req.url,
372 stack: err.stack,
373 req_headers: req.headers,
374 });
375 var contentType = me.getContentType(req) || false;
376 err.status = err.status || 500;
377 err.message = me.isDevel() ? err.message : "Error with status " + err.status + " has been set";
378 err.stack = me.isDevel() ? err.stack : null;
379 res.setHeader("Content-Type", contentType || "");
380 res.status(err.status);
381 res.setHeader("Cache-Control", "private, no-cache, no-store, max-age=0, proxy-revalidate, s-maxage=0");
382 res.setHeader("Expires", "Sat, 12 Oct 1991 05:00:00 GMT");
383 res.setHeader("Pragma", "no-cache");
384 res.setHeader("Vary", "*");
385 if (contentType && contentType.indexOf("application/json") !== -1) {
386 me.handleFinalJSONError.apply(me, arguments);
387 }
388 else {
389 me.handleFinalHTMLError.apply(me, arguments);
390 }
391 });
392 };
393 HttpApplication.prototype.createRuntimeFolders = function () {
394 var me = this;
395 me.varFolder = Utils_1.Utils.ensureFolder(me.options.runtimeFolder);
396 me.expressApp.set(HttpApplication.KEY_RUNTIME_FOLDER, me.varFolder);
397 };
398 HttpApplication.prototype.start = function (done) {
399 var me = this;
400 if (!me.running) {
401 me.httpServer = Http.createServer(me.expressApp);
402 me.httpServer.on("error", function () {
403 me.onError.apply(me, arguments);
404 });
405 me.httpServer.on("listening", function () {
406 var addr = me.httpServer.address();
407 me.logger.info("%s is ready for connections on address: %s port: %s", me.appName, addr.address, addr.port);
408 if (me.onStart.apply(me, arguments) === true) {
409 me.running = true;
410 if (done) {
411 done();
412 }
413 }
414 });
415 if (me.configureApplicationInternal()) {
416 me.configureApplication();
417 me.configure404StatusProvider();
418 me.configureFinalErrorProvider();
419 me.httpServer.listen(me.options.port, me.options.address);
420 }
421 }
422 else {
423 if (done) {
424 done();
425 }
426 }
427 };
428 HttpApplication.prototype.stop = function (done) {
429 var me = this;
430 if (me.running) {
431 me.httpServer.close(function () {
432 me.onStop.apply(me, arguments);
433 me.logger.info("%s is shutdown.", me.appName);
434 me.logger.close();
435 winston.loggers.close("http-logger");
436 me.running = false;
437 if (done) {
438 done();
439 }
440 });
441 }
442 else {
443 if (done) {
444 done();
445 }
446 }
447 };
448 HttpApplication.prototype.onError = function (error) {
449 var me = this, msg = "";
450 if (error.syscall !== "listen") {
451 throw error;
452 }
453 switch (error.code) {
454 case "EACCES":
455 msg = me.options.port + " requires elevated privileges!";
456 me.logger.error(msg);
457 console.error(msg);
458 process.exit(1);
459 break;
460 case "EADDRINUSE":
461 msg = me.options.port + " is already in use!";
462 me.logger.error(msg);
463 console.error(msg);
464 process.exit(1);
465 break;
466 default:
467 throw error;
468 }
469 };
470 ;
471 HttpApplication.prototype.onStart = function () {
472 return true;
473 };
474 HttpApplication.prototype.onStop = function () {
475 };
476 return HttpApplication;
477}());
478HttpApplication.KEY_RUNTIME_FOLDER = "_runtime_folder";
479HttpApplication.KEY_ROOT_FOLDER = "_root_folder";
480HttpApplication.KEY_LOG_FOLDER = "_log_folder";
481HttpApplication.KEY_APPLICATION_NAME = "_application_name";
482HttpApplication.KEY_AUTH_USER = "_auth_user";
483HttpApplication.KEY_AVAILABLE_LOCALES = "_available_locales";
484HttpApplication.KEY_LOCALE = "_locale";
485HttpApplication.KEY_I18N = "_i18n";
486exports.HttpApplication = HttpApplication;
487//# sourceMappingURL=HttpApplication.js.map
\No newline at end of file