UNPKG

19.8 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 file = fs.realpathSync(value.replace(k, tokens[k]));
235 value = fs.readFileSync(file).toLocaleString();
236 }
237 }
238 return value;
239 };
240 var procesData = function (data, file) {
241 for (var msg in data) {
242 for (var loc in data[msg]) {
243 if (!db[loc]) {
244 db[loc] = {};
245 }
246 db[loc][msg] = parseValue(data[msg][loc], path.dirname(file));
247 }
248 }
249 };
250 folders.forEach(function (item) {
251 glob.sync(item).forEach(function (file) {
252 try {
253 procesData(JSON.parse(fs.readFileSync(file).toLocaleString()), file);
254 }
255 catch (e) {
256 me.logger.error(e.message);
257 }
258 });
259 });
260 me.expressApp.set(HttpApplication.KEY_I18N, db);
261 };
262 HttpApplication.prototype.translate = function (req, message) {
263 var args = [];
264 for (var _i = 2; _i < arguments.length; _i++) {
265 args[_i - 2] = arguments[_i];
266 }
267 return util.format.apply(util.format, [((req.app.get(HttpApplication.KEY_I18N) || {})[this.getCurrentLocale(req)] || {})[message] || message].concat(args));
268 };
269 HttpApplication.prototype.configureLocaleProvider = function (parameterName, defaultLocale, availableLocales) {
270 var me = this;
271 me.expressApp.use(function (req, res, next) {
272 var accept_language = me.getAcceptedLangugeHeader(req);
273 defaultLocale = defaultLocale || accept_language || "en";
274 availableLocales = availableLocales || [defaultLocale];
275 if (availableLocales.indexOf(defaultLocale) === -1) {
276 availableLocales.push(defaultLocale);
277 }
278 var get_param = req.query[parameterName] || null;
279 var post_param = req.body[parameterName] || null;
280 var sess_param = me.getCurrentLocale(req) || null;
281 var value = get_param || post_param || sess_param || accept_language || defaultLocale;
282 value = availableLocales.indexOf(value) !== -1 ? value : defaultLocale;
283 req.session[HttpApplication.KEY_LOCALE] = value;
284 if (!me.getAvailableLocales(req)) {
285 req.app.set(HttpApplication.KEY_AVAILABLE_LOCALES, availableLocales);
286 }
287 next();
288 });
289 return true;
290 };
291 HttpApplication.prototype.configureStaticFolderProvider = function (folders) {
292 var me = this;
293 Object.keys(folders).forEach(function (mapping) {
294 me.logger.info("Serving %s as %s", folders[mapping], mapping);
295 if (mapping === "/") {
296 me.expressApp.use(express.static(folders[mapping], {
297 fallthrough: true
298 }));
299 }
300 else {
301 me.expressApp.use("/" + mapping, express.static(folders[mapping], {
302 fallthrough: false
303 }));
304 }
305 });
306 return true;
307 };
308 HttpApplication.prototype.configureFaciconProvider = function (faviconPath) {
309 var me = this;
310 me.expressApp.use(serveFavicon(faviconPath));
311 };
312 HttpApplication.prototype.configurePugTemplatesProvider = function (pugOptions) {
313 var me = this;
314 me.expressApp.set("view engine", "pug");
315 me.expressApp.set("view options", pugOptions || {});
316 };
317 HttpApplication.prototype.configureApplicationInternal = function () {
318 var me = this, ok = false;
319 if (me.configureHelmetSecurity()) {
320 if (me.configureHttpLogger()) {
321 if (me.configureContentParsers()) {
322 if (me.configureSessionContainer()) {
323 if (me.configureAuthenticatedUser()) {
324 ok = true;
325 }
326 }
327 }
328 }
329 }
330 return ok;
331 };
332 HttpApplication.prototype.configure404StatusProvider = function () {
333 var me = this;
334 me.expressApp.use(function (req, res, next) {
335 var err = new Error(req.originalUrl + " Not Found!");
336 err.status = 404;
337 next(err);
338 });
339 };
340 HttpApplication.prototype.handleFinalJSONError = function (err, req, res, next) {
341 var me = this;
342 res.json({
343 error: true,
344 message: err.mesage,
345 status: err.status,
346 stack: me.isDevel() ? err.stack : null
347 });
348 };
349 HttpApplication.prototype.handleFinalHTMLError = function (err, req, res, next) {
350 var me = this, errorViewFile = me.httpErrorViews[err.status];
351 fs.exists(errorViewFile, function (exists) {
352 if (exists) {
353 res.render(errorViewFile, {
354 appName: me.appName,
355 err: err,
356 locale: me.getCurrentLocale(req) || "en"
357 });
358 }
359 else {
360 res.send("Error: " + err.status + "\nMessage: " + err.message + "\nStack: " + (me.isDevel() ? err.stack : ""));
361 }
362 });
363 };
364 HttpApplication.prototype.configureFinalErrorProvider = function () {
365 var me = this;
366 me.expressApp.use(function (err, req, res, next) {
367 me.logger.error("Error", {
368 status: err.status,
369 message: err.message,
370 url: req.url,
371 stack: err.stack,
372 req_headers: req.headers,
373 });
374 var contentType = me.getContentType(req) || false;
375 err.status = err.status || 500;
376 err.message = me.isDevel() ? err.message : "Error with status " + err.status + " has been set";
377 err.stack = me.isDevel() ? err.stack : null;
378 res.setHeader("Content-Type", contentType || "");
379 res.status(err.status);
380 res.setHeader("Cache-Control", "private, no-cache, no-store, max-age=0, proxy-revalidate, s-maxage=0");
381 res.setHeader("Expires", "Sat, 12 Oct 1991 05:00:00 GMT");
382 res.setHeader("Pragma", "no-cache");
383 res.setHeader("Vary", "*");
384 if (contentType && contentType.indexOf("application/json") !== -1) {
385 me.handleFinalJSONError.apply(me, arguments);
386 }
387 else {
388 me.handleFinalHTMLError.apply(me, arguments);
389 }
390 });
391 };
392 HttpApplication.prototype.createRuntimeFolders = function () {
393 var me = this;
394 me.varFolder = Utils_1.Utils.ensureFolder(me.options.runtimeFolder);
395 me.expressApp.set(HttpApplication.KEY_RUNTIME_FOLDER, me.varFolder);
396 };
397 HttpApplication.prototype.start = function (done) {
398 var me = this;
399 if (!me.running) {
400 me.httpServer = Http.createServer(me.expressApp);
401 me.httpServer.on("error", function () {
402 me.onError.apply(me, arguments);
403 });
404 me.httpServer.on("listening", function () {
405 var addr = me.httpServer.address();
406 me.logger.info("%s is ready for connections on address: %s port: %s", me.appName, addr.address, addr.port);
407 if (me.isDevel()) {
408 console.log(util.format("%s is ready for connections on address: %s port: %s", me.appName, addr.address, addr.port));
409 }
410 if (me.onStart.apply(me, arguments) === true) {
411 me.running = true;
412 if (done) {
413 done();
414 }
415 }
416 });
417 if (me.configureApplicationInternal()) {
418 me.configureApplication();
419 me.configure404StatusProvider();
420 me.configureFinalErrorProvider();
421 me.httpServer.listen(me.options.port, me.options.address);
422 }
423 }
424 else {
425 if (done) {
426 done();
427 }
428 }
429 };
430 HttpApplication.prototype.stop = function (done) {
431 var me = this;
432 if (me.running) {
433 me.httpServer.close(function () {
434 me.onStop.apply(me, arguments);
435 me.logger.info("%s is shutdown.", me.appName);
436 me.logger.close();
437 winston.loggers.close("http-logger");
438 me.running = false;
439 if (done) {
440 done();
441 }
442 });
443 }
444 else {
445 if (done) {
446 done();
447 }
448 }
449 };
450 HttpApplication.prototype.onError = function (error) {
451 var me = this, msg = "";
452 if (error.syscall !== "listen") {
453 throw error;
454 }
455 switch (error.code) {
456 case "EACCES":
457 msg = me.options.port + " requires elevated privileges!";
458 me.logger.error(msg);
459 console.error(msg);
460 process.exit(1);
461 break;
462 case "EADDRINUSE":
463 msg = me.options.port + " is already in use!";
464 me.logger.error(msg);
465 console.error(msg);
466 process.exit(1);
467 break;
468 default:
469 throw error;
470 }
471 };
472 ;
473 HttpApplication.prototype.onStart = function () {
474 return true;
475 };
476 HttpApplication.prototype.onStop = function () {
477 };
478 return HttpApplication;
479}());
480HttpApplication.KEY_RUNTIME_FOLDER = "_runtime_folder";
481HttpApplication.KEY_ROOT_FOLDER = "_root_folder";
482HttpApplication.KEY_LOG_FOLDER = "_log_folder";
483HttpApplication.KEY_APPLICATION_NAME = "_application_name";
484HttpApplication.KEY_AUTH_USER = "_auth_user";
485HttpApplication.KEY_AVAILABLE_LOCALES = "_available_locales";
486HttpApplication.KEY_LOCALE = "_locale";
487HttpApplication.KEY_I18N = "_i18n";
488exports.HttpApplication = HttpApplication;
489//# sourceMappingURL=HttpApplication.js.map
\No newline at end of file