UNPKG

9.37 kBJavaScriptView Raw
1'use strict';
2
3let path = require('path');
4let fs = require('fs');
5
6let log = require('logtopus').getLogger('express-server');
7let co = require('co-utils');
8let fileExists = require('fs').existsSync;
9let express = require('express');
10let glob = require('glob');
11let extend = require('node.extend');
12
13
14//Catch uncaught errors
15process.on('uncaughtException', function(err) {
16 log.error(err.name + ': ' + err.message, err.stack.replace(/.*\n/, '\n'));
17 console.error(err.name + ': ' + err.message, err.stack.replace(/.*\n/, '\n')); // eslint-disable-line
18});
19
20let app;
21
22let ExpressServer = function(conf) {
23 conf = conf || {};
24
25 //Base dir
26 this.baseDir = conf.baseDir || process.cwd();
27 this.publicDir = conf.publicDir || path.join(this.baseDir, 'public');
28
29 //Load config file
30 let serverConf = this.getConfig(conf.confDir);
31 conf = extend({}, serverConf, conf);
32 this.conf = conf;
33
34 if (conf.logLevel) {
35 log.setLevel(conf.logLevel);
36 }
37 else {
38 log.setLevel('sys');
39 }
40
41 log.sys('Set loglevel to ' + log.getLevel());
42
43 if (this.confFile) {
44 log.sys('Read config file:', this.confFile);
45 }
46 else {
47 log.sys('No config file found!');
48 this._confFiles.map(function(dir) {
49 log.sys(' ... ' + dir);
50 });
51 }
52
53 //Default port
54 this.port = conf.port || 3000;
55
56 //Default name
57 this.name = conf.name || 'Express server';
58
59 //API route (Disabled by default)
60 this.apiInfo = conf.apiInfo || null;
61
62 //Request logging config
63 if (conf.requestLog) {
64 this.requestLogFile = conf.requestLog === true ? path.join(this.baseDir, 'log', 'request.log') : conf.requestLog;
65 if (this.requestLogFile.charAt(0) === '.') {
66 this.requestLogFile = path.join(this.baseDir, this.requestLogFile);
67 }
68
69 this.requestLogIgnore = {
70 contentType: null
71 };
72 }
73
74 //Enable user tracking
75 if (conf.userTracking) {
76 this.userTracking = conf.userTracking === true ? path.join(this.baseDir, 'log', 'usertracking.log') : conf.userTracking;
77 if (this.userTracking.charAt(0) === '.') {
78 this.userTracking = path.join(this.baseDir, this.userTracking);
79 }
80 }
81
82 this.allRoutes = [];
83 this.routes = [];
84
85 app = express();
86 app.express = express;
87 app.logger = log;
88 app.conf = conf;
89 this.app = app;
90
91 this.__context = function() {
92 log.warn('Using callback is deprecated! Simply return a promise if method is async');
93 };
94};
95
96ExpressServer.prototype.setContext = function (ctx) {
97 this.__context = ctx;
98};
99
100/**
101 * Starts an express server
102 *
103 * @method start
104 */
105ExpressServer.prototype.start = function(opts, callback) {
106 if (typeof opts === 'function') {
107 callback = opts;
108 opts = null;
109 }
110
111 opts = opts || {
112 disableServer: false
113 };
114
115 if (!callback) {
116 callback = function() {};
117 }
118
119 app.addRoute = function(method, path, info, options, callback) {
120 log.warn('app.addRoute() was deprecated! Use default app.VERB methods instead');
121 if (typeof options === 'function') {
122 callback = options;
123 options = null;
124 }
125
126 var fn = Array.prototype.slice.call(arguments);
127 while(fn.length > 0 && typeof fn[0] !== 'function') {
128 fn.shift();
129 }
130
131 fn.unshift(path);
132 app[method.toLowerCase()].apply(app, fn);
133 this.allRoutes.push({
134 method: method,
135 path: path,
136 info: info,
137 options: options,
138 callback: callback
139 });
140 }.bind(this);
141
142 log.sys('Starting ' + this.name);
143 log.sys(' ... environment:', app.get('env'));
144 log.sys(' ... set base dir to:', this.baseDir);
145
146 app.baseDir = this.baseDir;
147
148 // Set express-server static dir
149 app.use('/express-server', express.static(path.join(__dirname, 'public')));
150
151 // Enable request logging
152 if (this.requestLogFile) {
153 log.sys(' ... log requests to:', this.requestLogFile);
154 app.use(this.requestLogger.bind(this));
155 }
156
157 //Log requests to console?
158 if (true) {
159 app.use(require('logtopus').express({
160 logLevel: this.conf.logLevel
161 }));
162 }
163
164 co(function* () {
165 // Load Environment configuration
166 let envConf = path.join(this.baseDir, 'server/env', app.get('env') + '.js');
167 if (fileExists(envConf)) {
168 log.sys(' ... load environment config');
169 let task = require(envConf).call(this, app, this.__context);
170 if (task && task.then) {
171 yield task;
172 }
173 }
174 else {
175 log.sys(' ... no environment config found!');
176 }
177
178 //Load express.js
179 let expressFile = path.join(app.baseDir, 'server/express.js');
180 if (fileExists(expressFile)) {
181 log.sys(' ... load express config');
182 let task = require(expressFile).call(this, app, this.__context);
183 if (task && task.then) {
184 yield task;
185 }
186 }
187
188 //Load database.js
189 let databaseFile = path.join(app.baseDir, 'server/database.js');
190 if (fileExists(databaseFile)) {
191 log.sys(' ... load database config');
192 let task = require(databaseFile).call(this, app, this.__context);
193 if (task && task.then) {
194 yield task;
195 }
196 }
197
198 //Load routes
199 if (opts.disableRoutes !== true) {
200 let routesDir = path.join(this.baseDir, 'routes/**/*.js');
201 let files = glob.sync(routesDir);
202 this.routerFiles = files;
203 if (files.length !== 0) {
204 for (let file of files) {
205 log.sys(' ... load route file', file);
206 let task = require(file).call(this, app, this.__context);
207 if (task && task.then) {
208 yield task;
209 }
210 }
211 }
212 else if (this.routes.length === 0) {
213 log.sys(' ... no routes found');
214 }
215 }
216
217 //Load custom routes
218 if (this.routes && this.routes.length) {
219 this.routes.forEach(function(route) {
220 log.sys(' ... add route', ' '.repeat(6 - route.method.length) + route.method + ' ' + route.route);
221 let args = [route.route].concat(route.funcs);
222 this.app[route.method.toLowerCase()].apply(this.app, args);
223 }, this);
224 }
225
226 //Load API view
227 if (this.apiInfo) {
228 log.sys(' ... register api route', this.apiInfo);
229 let task = require(path.join(__dirname, 'routes/api')).call(this, app);
230 if (task && task.then) {
231 yield task;
232 }
233 }
234
235 //Enable user tracking
236 if (this.userTracking) {
237 this.trackingRoute = this.trackingRoute || '/track';
238 log.sys(' ... track user to:', this.userTracking);
239 let task = require(path.join(__dirname, 'routes/tracking')).call(this, app);
240 if (task && task.then) {
241 yield task;
242 }
243 }
244 }.bind(this)).then(result => {
245 app.use(function(err, req, res, next) {
246 log.error(err.stack);
247 res.status(500).send('Something broke!\n');
248 });
249
250 if (opts.disableServer !== true) {
251 log.sys(' ... listening on port ', this.port);
252 log.sys('Server started successfull!');
253 app.listen(this.port);
254 }
255
256 //Load init script
257 let initFile = path.join(app.baseDir, 'server/init.js');
258 if (fileExists(initFile)) {
259 require(initFile)(app, callback.bind(this, app));
260 }
261 else {
262 callback.call(this, app);
263 }
264 }).catch(err => {
265 log.error('Can\'t boot the server.');
266 log.error(err.stack || err);
267 process.exit(1);
268 });
269};
270
271/**
272 * Stopping express server
273 */
274ExpressServer.prototype.stop = function() {
275 log.sys('Stoping ' + this.name);
276};
277
278/**
279 * Handle JSON result
280 */
281ExpressServer.prototype.handleJSONResult = function(err, data) {
282 if (err) {
283 log.err(err);
284 this.send(500, 'Something went wrong!');
285 return;
286 }
287
288 this.json(data);
289};
290
291ExpressServer.prototype.requestLogger = function(req, res, next) {
292 var startTime = Date.now(),
293 logFile = this.requestLogFile,
294 ignoreContentTypes = this.requestLogIgnore.contentType;
295
296 var LogIt = function() {
297 var parseTime = Date.now() - startTime,
298 contentType = res.get('Content-Type') || '';
299
300 res.removeListener('finish', LogIt);
301
302 if (ignoreContentTypes && ignoreContentTypes.indexOf(contentType) !== -1) {
303 return;
304 }
305
306 var data = '[' + (new Date()).toString() + ']';
307 data += ' ' + res.statusCode;
308 data += ' ' + parseTime + 'ms';
309 data += ' ' + req.method;
310 data += ' "' + req.protocol;
311 data += '://' + req.get('host');
312 data += req.originalUrl + '"';
313 data += ' "' + contentType + '"';
314 data += ' "' + req.get('user-agent') + '"';
315 data += ' "' + req.get('referer') + '"';
316 if (req.sessionID) {
317 data += ' (' + req.sessionID + ')';
318 }
319 data += '\n';
320
321 fs.appendFile(logFile, data, function() {});
322 };
323
324 res.on('finish', LogIt);
325
326 next();
327};
328
329ExpressServer.prototype.getConfig = function(confDir) {
330 this._confFiles = [];
331 var confFile = (process.env.NODE_ENV || 'development') + '.json';
332 if (confDir) {
333 this._confFiles.push(path.join(confDir, confFile));
334 }
335
336 this._confFiles.push(path.join(this.baseDir, 'config', confFile));
337 this._confFiles.push(path.join(this.baseDir, '../config', confFile));
338
339 for (var i = 0, len = this._confFiles.length; i < len; i++) {
340 if (fs.existsSync(this._confFiles[i])) {
341 this.confFile = this._confFiles[i];
342 return require(this._confFiles[i]);
343 }
344 }
345
346 return {};
347};
348
349ExpressServer.prototype.addRoute = function(method, route, fn) {
350 var funcs = Array.prototype.slice.call(arguments, 2);
351 this.routes.push({
352 method: method,
353 route: route,
354 funcs: funcs
355 });
356};
357
358module.exports = ExpressServer;