1 |
|
2 | 'use strict';
|
3 |
|
4 | const cluster = require('./cluster');
|
5 | const http = require('http');
|
6 | const https = require('https');
|
7 | const express = require('express');
|
8 | const morgan = require('morgan');
|
9 | const FileStreamRotator = require('file-stream-rotator');
|
10 | const PrettyError = require('pretty-error');
|
11 | const favicon = require('serve-favicon');
|
12 | const cookieParser = require('cookie-parser');
|
13 | const bodyParser = require('body-parser');
|
14 | const compress = require('compression');
|
15 | const session = require('express-session');
|
16 | const cors = require('cors');
|
17 | const helmet = require('helmet');
|
18 | const lusca = require('lusca');
|
19 | const chalk = require('chalk');
|
20 | const fs = require('fs');
|
21 | const path = require('path');
|
22 | const extensionParser = require('mio-extensions');
|
23 | const jwt = require('jwt-simple');
|
24 | const netjet = require('netjet');
|
25 | const rateLimit = require('express-rate-limit');
|
26 | const killSniff = require('mio-killsniff');
|
27 | const winston = require('winston');
|
28 | const moduleComposer = require('./moduleComposer');
|
29 |
|
30 |
|
31 | var bannedModules = [];
|
32 | function _canILoadModule(name){
|
33 | if(!bannedModules || bannedModules.length==0 || bannedModules.indexOf(name)==-1){
|
34 | return true;
|
35 | }
|
36 |
|
37 | return false;
|
38 | }
|
39 |
|
40 |
|
41 | function create(){
|
42 | cluster.printOnce(()=>{
|
43 | winston.info('-'.repeat(70));
|
44 | winston.info('Init WebServer on', chalk.blue(global.mio.config.http.port), 'port');
|
45 | });
|
46 |
|
47 |
|
48 | var app = express();
|
49 | global.mio.app = app;
|
50 | app.set('port', global.mio.config.http.port);
|
51 | var server;
|
52 |
|
53 |
|
54 | if(global.mio.config.http.ssl){
|
55 | var privateKeyFile = path.join(__dirname, '../', global.mio.config.http.ssl.privateKey);
|
56 | var certificateFile = path.join(__dirname, '../', global.mio.config.http.ssl.certificate);
|
57 |
|
58 | var privateKey = fs.readFileSync( privateKeyFile );
|
59 | var certificate = fs.readFileSync( certificateFile );
|
60 |
|
61 | server = https.createServer({
|
62 | key: privateKey,
|
63 | cert: certificate
|
64 | }, app);
|
65 | }else{
|
66 | server = http.createServer(app);
|
67 | }
|
68 | server.listen(global.mio.config.http.port);
|
69 |
|
70 |
|
71 | server.on('error', onError);
|
72 | function onError(error) {
|
73 | if (error.syscall !== 'listen') {
|
74 | throw error;
|
75 | }
|
76 |
|
77 | var bind = typeof port === 'string'
|
78 | ? 'Pipe ' + global.mio.config.http.port
|
79 | : 'Port ' + global.mio.config.http.port;
|
80 |
|
81 |
|
82 | switch (error.code) {
|
83 | case 'EACCES':
|
84 | winston.error(bind + ' requires elevated privileges');
|
85 | process.exit(1);
|
86 | break;
|
87 | case 'EADDRINUSE':
|
88 | winston.error(bind + ' is already in use');
|
89 | process.exit(1);
|
90 | break;
|
91 | default:
|
92 | throw error;
|
93 | }
|
94 | }
|
95 |
|
96 |
|
97 | app.locals.http = {modules: []};
|
98 |
|
99 | return app;
|
100 | }
|
101 |
|
102 |
|
103 | function loadTemplateEngine(app){
|
104 | app.set('view engine', 'vash');
|
105 | app.set('views', global.mio.appPath);
|
106 |
|
107 | app.locals.http.modules.push('vash');
|
108 | }
|
109 |
|
110 |
|
111 | function loadParsersModules(app){
|
112 |
|
113 | if(_canILoadModule('bodyParser')){
|
114 | app.use(bodyParser.json({ limit: (global.mio.config.http.maxBodySize || '5mb') }));
|
115 | app.use(bodyParser.urlencoded({ extended: false }));
|
116 | app.locals.http.modules.push('bodyParser('+(global.mio.config.http.maxBodySize || '5mb')+')');
|
117 | }
|
118 |
|
119 |
|
120 | if(_canILoadModule('compress')){
|
121 | app.use(compress());
|
122 | app.locals.http.modules.push('compress');
|
123 | }
|
124 |
|
125 |
|
126 | if(_canILoadModule('extensionParser')){
|
127 | app.use(extensionParser.extract);
|
128 | app.use((req, res, next)=>{
|
129 | req._ext = extensionParser.render;
|
130 | return next();
|
131 | });
|
132 | app.locals.http.modules.push('extensionParser');
|
133 | }
|
134 | }
|
135 |
|
136 |
|
137 | function loadStorageModules(app){
|
138 |
|
139 | if(_canILoadModule('cookieParser')){
|
140 | app.use(cookieParser());
|
141 | app.locals.http.modules.push('cookieParser');
|
142 | }
|
143 |
|
144 |
|
145 | if(_canILoadModule('sessions')){
|
146 | app.use(session({
|
147 | secret: global.mio.config.solt.sessions,
|
148 | resave: true,
|
149 | saveUninitialized: true,
|
150 | cookie: { secure: true }
|
151 | }));
|
152 | app.locals.http.modules.push('sessions');
|
153 | }
|
154 | }
|
155 |
|
156 |
|
157 | function loadSecurityModules(app){
|
158 |
|
159 | if(_canILoadModule('killSniff')){
|
160 | app.use(killSniff({domains: global.mio.config.domains}));
|
161 | app.locals.http.modules.push('killSniff');
|
162 | }
|
163 |
|
164 |
|
165 | if(_canILoadModule('trustProxy')){
|
166 | app.set('trust proxy', 1);
|
167 | app.locals.http.modules.push('trustProxy(1)');
|
168 | }
|
169 |
|
170 |
|
171 | if(_canILoadModule('limiter')){
|
172 | var limiter = new rateLimit({
|
173 | windowMs: 15*60*1000,
|
174 | max: 300,
|
175 | delayMs: 0
|
176 | });
|
177 | app.use(limiter);
|
178 | app.locals.http.modules.push('limiter');
|
179 | }
|
180 |
|
181 |
|
182 | if(_canILoadModule('cors')){
|
183 | app.use(cors());
|
184 | app.locals.http.modules.push('cors');
|
185 | }
|
186 |
|
187 |
|
188 | if(_canILoadModule('helmet')){
|
189 | app.use(helmet());
|
190 | app.locals.http.modules.push('helmet');
|
191 | }
|
192 |
|
193 |
|
194 | if(_canILoadModule('lusca')){
|
195 | app.use(lusca({
|
196 | csrf: true,
|
197 | xframe: 'SAMEORIGIN',
|
198 | p3p: 'ABCDEF',
|
199 | hsts: {maxAge: 31536000, includeSubDomains: true, preload: true},
|
200 | xssProtection: true,
|
201 | nosniff: true
|
202 | }));
|
203 | app.locals.http.modules.push('lusca');
|
204 | }
|
205 |
|
206 |
|
207 | if(_canILoadModule('JWT')){
|
208 | app.use((req, res, next)=>{
|
209 | if(req.cookies.token){
|
210 | req.user = jwt.decode(req.cookies.token, global.mio.config.solt.jwt);
|
211 |
|
212 | if(req.user.expire && req.user.expire < Date.now()){
|
213 | return res.end('Access token has expired', 400);
|
214 | }
|
215 | }
|
216 |
|
217 | return next();
|
218 | });
|
219 |
|
220 | app.locals.http.modules.push('JWT');
|
221 | }
|
222 | }
|
223 |
|
224 |
|
225 | function loadDebugModules(app){
|
226 |
|
227 | if(_canILoadModule('morgan')){
|
228 | if(global.mio.config.http.logFile){
|
229 | var logFile;
|
230 | if (fs.existsSync('/var/log/'+global.name)) {
|
231 | logFile = path.join('/var/log/', global.name, global.mio.config.http.logFile+'-%DATE%.log');
|
232 | }else{
|
233 | logFile = path.join(global.mio.appPath, '/log/', global.mio.config.http.logFile+'-%DATE%.log');
|
234 | }
|
235 |
|
236 |
|
237 | var accessLogStream = FileStreamRotator.getStream({
|
238 | date_format: 'YYYYMMDD',
|
239 | filename: logFile,
|
240 | frequency: 'daily',
|
241 | verbose: false
|
242 | });
|
243 |
|
244 | app.use(morgan('combined', {stream: accessLogStream}));
|
245 | app.locals.http.modules.push('morgan('+global.mio.config.http.logFile+')');
|
246 | }else{
|
247 | app.use(morgan('dev'));
|
248 | app.locals.http.modules.push('morgan');
|
249 | }
|
250 | }
|
251 |
|
252 |
|
253 | if(_canILoadModule('showStackError')){
|
254 | app.set('showStackError', true);
|
255 | app.locals.http.modules.push('showStackError');
|
256 | }
|
257 |
|
258 |
|
259 | if(_canILoadModule('status-page')){
|
260 | app.use(require('express-status-monitor')({title: global.name+' Status'}));
|
261 | app.locals.http.modules.push('status-page');
|
262 | }
|
263 | }
|
264 |
|
265 |
|
266 | function loadBoosters(app){
|
267 |
|
268 | if(_canILoadModule('netjet')){
|
269 | app.use(netjet());
|
270 | app.locals.http.modules.push('netjet');
|
271 | }
|
272 | }
|
273 |
|
274 |
|
275 | function loadedModules(app){
|
276 |
|
277 | cluster.printOnce(()=>{
|
278 | winston.info('HTTP Modules:', chalk.green(app.locals.http.modules.join(', ')));
|
279 | });
|
280 | }
|
281 |
|
282 |
|
283 | function loadStatic(app){
|
284 | var staticFilesPath = path.join(global.mio.appPath, global.mio.config.http.assets.static);
|
285 |
|
286 |
|
287 | if(_canILoadModule('favicon')){
|
288 | app.use(favicon(staticFilesPath + global.mio.config.http.assets.favicon));
|
289 | }
|
290 |
|
291 |
|
292 | if(_canILoadModule('static')){
|
293 | app.use('/', express.static(staticFilesPath));
|
294 | }
|
295 |
|
296 | cluster.printOnce(()=>{
|
297 | winston.info('HTTP Static: '+ chalk.blue(staticFilesPath));
|
298 | });
|
299 | }
|
300 |
|
301 |
|
302 | function loadRoutes(app){
|
303 |
|
304 | var modulesNames = [];
|
305 |
|
306 |
|
307 | for (var mod in moduleComposer.modules) {
|
308 | if(_canILoadModule(mod)){
|
309 | if (moduleComposer.modules.hasOwnProperty(mod)) {
|
310 | if(moduleComposer.modules[mod].routes){
|
311 | app.use(moduleComposer.modules[mod].routes);
|
312 | modulesNames.push(mod);
|
313 | }
|
314 | }
|
315 | }
|
316 | }
|
317 |
|
318 | cluster.printOnce(()=>{
|
319 | winston.info('Loaded routes for modules:', chalk.green(modulesNames.join(', ')));
|
320 | });
|
321 | }
|
322 |
|
323 |
|
324 | function loadErrorHandlers(app){
|
325 |
|
326 | app.use((req, res, next)=>{
|
327 | var err = new Error('Not Found');
|
328 | err.status = 404;
|
329 | next(err);
|
330 | });
|
331 |
|
332 |
|
333 | var pe = new PrettyError();
|
334 | pe.skipNodeFiles();
|
335 | pe.skipPackage('express');
|
336 |
|
337 |
|
338 | app.use(function (err, req, res, next) {
|
339 |
|
340 | if (!err) return next();
|
341 |
|
342 |
|
343 | winston.info(pe.render(err));
|
344 |
|
345 |
|
346 | if(err.status){
|
347 | global.mio.error(req, res, err.status);
|
348 | }else{
|
349 | global.mio.error(req, res, 404);
|
350 | }
|
351 | });
|
352 | }
|
353 |
|
354 |
|
355 | function init(){
|
356 | const app = create();
|
357 |
|
358 |
|
359 | bannedModules = global.mio.config.http.bannedModules || [];
|
360 |
|
361 |
|
362 | loadTemplateEngine(app);
|
363 | loadParsersModules(app);
|
364 | loadStorageModules(app);
|
365 | loadSecurityModules(app);
|
366 | loadDebugModules(app);
|
367 | loadBoosters(app);
|
368 | loadedModules(app);
|
369 |
|
370 |
|
371 | loadStatic(app);
|
372 | loadRoutes(app, config);
|
373 | loadErrorHandlers(app);
|
374 |
|
375 | return app;
|
376 | }
|
377 |
|
378 | module.exports = init();
|