1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 | var cluster = require('cluster'),
|
18 | fs = require('fs'),
|
19 | path = require('path'),
|
20 | Express = require('express'),
|
21 | _ = require('underscore'),
|
22 | cookieParser = require('cookie-parser'),
|
23 | errorHandler = require('errorhandler'),
|
24 | compression = require('compression'),
|
25 | staticFavicon = require('serve-favicon'),
|
26 | bodyParser = require('body-parser'),
|
27 | Promise = require('bluebird'),
|
28 | Backhoe = require('backhoe'),
|
29 | Config = require('./config'),
|
30 | Controller = require('./controller'),
|
31 | Environment = require('./environment'),
|
32 | Router = require('./router'),
|
33 | Logger = require('./logger'),
|
34 | Template = require('./template'),
|
35 | Filter = require('./filter'),
|
36 | Path = require('./path'),
|
37 | FileSystem = require('./filesystem'),
|
38 | jadePlugin = require('./minorjs_template_jade');
|
39 |
|
40 |
|
41 |
|
42 |
|
43 | var Minor = function(options) {
|
44 | this.app = Express();
|
45 | this.options = options;
|
46 | this.closing = false;
|
47 |
|
48 |
|
49 | this.listen = _.bind(this.listen, this);
|
50 | this.initialize = _.bind(this.initialize, this);
|
51 |
|
52 | if (!this.options.instance) {
|
53 | this.options.instance = 1;
|
54 | }
|
55 |
|
56 | if (!this.options.controllerTimeout) {
|
57 |
|
58 | this.options.controllerTimeout = 180000;
|
59 | }
|
60 |
|
61 | if (!this.options.hostname) {
|
62 | this.options.hostname = '0.0.0.0';
|
63 | }
|
64 |
|
65 | this._initializeEnvironment();
|
66 | this._initializeErrorHandling();
|
67 | };
|
68 |
|
69 | _.extend(Minor.prototype, {
|
70 |
|
71 |
|
72 |
|
73 |
|
74 |
|
75 | |
76 |
|
77 |
|
78 | initialize : function() {
|
79 | var start = Date.now(),
|
80 | defaultPromise,
|
81 | environmentPromise;
|
82 |
|
83 | this.start = start;
|
84 |
|
85 | this.app.use(cookieParser());
|
86 |
|
87 | defaultPromise = this._initializeDefaultConfiguration();
|
88 |
|
89 | environmentPromise = this._isProduction()
|
90 | ? this._initializeProductionConfig()
|
91 | : this._initializeDevelopmentConfig();
|
92 |
|
93 |
|
94 | process.on('message', this._handleMessage.bind(this));
|
95 |
|
96 | return Promise.all([defaultPromise, environmentPromise])
|
97 | .then(function () {
|
98 | Logger.profile('Initialize MinorJS', start);
|
99 | });
|
100 | },
|
101 |
|
102 | |
103 |
|
104 |
|
105 | listen : function() {
|
106 | var self = this,
|
107 | start = Date.now();
|
108 |
|
109 | return Router.load(this.app, this.options.basePath + "/lib/controllers", this.options)
|
110 | .then(function startExpressListening() {
|
111 | Logger.profile('Load controllers', start);
|
112 |
|
113 | var port = self._getPort();
|
114 | self.server = self.app.listen(port, self.options.hostname);
|
115 |
|
116 | Logger.profile('Load MinorJS', self.start);
|
117 |
|
118 | Logger.info("MinorJS HTTP server listening on " + self.options.hostname + ":" + port);
|
119 | })
|
120 | .catch(function (error) {
|
121 | console.error('Error loading controllers and registering routes.\n\n', error.stack);
|
122 | process.exit(1);
|
123 | });
|
124 | },
|
125 |
|
126 |
|
127 |
|
128 |
|
129 |
|
130 | |
131 |
|
132 |
|
133 | _getEnvironment : function () {
|
134 | return typeof process.env.NODE_ENV === 'undefined'
|
135 | ? 'development'
|
136 | : process.env.NODE_ENV;
|
137 | },
|
138 |
|
139 | |
140 |
|
141 |
|
142 | _getPort : function() {
|
143 | if (this.options.port) {
|
144 | return this.options.port;
|
145 | }
|
146 |
|
147 | return Config.get('port');
|
148 | },
|
149 |
|
150 | |
151 |
|
152 |
|
153 | _initializeDefaultConfiguration : function() {
|
154 | var self = this,
|
155 | start = Date.now();
|
156 |
|
157 | return Promise.resolve()
|
158 | .then(function () {
|
159 | self.app.use(compression());
|
160 |
|
161 | self.app.set('views', self.options.basePath + '/lib/templates');
|
162 |
|
163 | self.app.locals.escapeAttributes = true;
|
164 |
|
165 | if (Array.isArray(self.options.templatePlugins)) {
|
166 | for (var index in self.options.templatePlugins) {
|
167 | var plugin = self.options.templatePlugins[index];
|
168 | plugin.register(self.app);
|
169 | }
|
170 | } else {
|
171 | jadePlugin.register(self.app);
|
172 | }
|
173 | })
|
174 | .then(function () {
|
175 |
|
176 | return Template.loadMixins(self.options.basePath + '/lib/template_mixins');
|
177 | })
|
178 | .then(function () {
|
179 |
|
180 | return Filter.load(self.options.basePath + '/lib/filters');
|
181 | })
|
182 | .then(function () {
|
183 | var faviconPath = self.options.basePath + '/public/favicon.ico';
|
184 |
|
185 | if (fs.existsSync(faviconPath)) {
|
186 | self.app.use(staticFavicon(faviconPath));
|
187 | }
|
188 |
|
189 |
|
190 | self.app.use(bodyParser.urlencoded({ extended: false }));
|
191 |
|
192 |
|
193 | self.app.use(bodyParser.json());
|
194 | })
|
195 | .then(function () {
|
196 | Logger.profile('Initialize default configuration', start);
|
197 | });
|
198 | },
|
199 |
|
200 | |
201 |
|
202 |
|
203 | _initializeDevelopmentConfig : function() {
|
204 | var self = this,
|
205 | start = Date.now(),
|
206 | noCacheDefaults = ['lib/controllers',
|
207 | 'lib/controller_helpers',
|
208 | 'lib/views',
|
209 | 'lib/templates'];
|
210 |
|
211 | return Promise.resolve()
|
212 | .then(function() {
|
213 | var noCachePaths = self.options.noCache || noCacheDefaults;
|
214 | Backhoe.noCache(
|
215 | Environment.getBasePath(),
|
216 | noCachePaths
|
217 | );
|
218 |
|
219 |
|
220 | self.app.locals.uglify = false;
|
221 |
|
222 | self.app.use(
|
223 | errorHandler({
|
224 | dumpExceptions : true,
|
225 | showStack : true
|
226 | })
|
227 | );
|
228 |
|
229 | return self._loadMiddleware('development');
|
230 | })
|
231 | .then(function () {
|
232 | Logger.profile('Initialize development configuration', start);
|
233 | });
|
234 | },
|
235 |
|
236 | |
237 |
|
238 |
|
239 | _initializeProductionConfig : function() {
|
240 | var self = this,
|
241 | start = Date.now();
|
242 |
|
243 | return Promise.resolve()
|
244 | .then(function() {
|
245 | self.app.locals.uglify = true;
|
246 | return self._loadMiddleware('production');
|
247 | })
|
248 | .then(function () {
|
249 | Logger.profile('Initialize production configuration', start);
|
250 | });
|
251 | },
|
252 |
|
253 | |
254 |
|
255 |
|
256 | _initializeEnvironment : function() {
|
257 |
|
258 | Environment.initialize({
|
259 | basePath : this.options.basePath,
|
260 | environment : this.app.get('env'),
|
261 | instance : this.options.instance,
|
262 | loggers : this.options.loggers || [],
|
263 | contextName : this.options.contextName
|
264 | });
|
265 | },
|
266 |
|
267 | |
268 |
|
269 |
|
270 | _initializeErrorHandling : function () {
|
271 | process.on('uncaughtException', function handleException (error) {
|
272 | console.log(error.stack)
|
273 |
|
274 | Logger.error(error.stack);
|
275 |
|
276 | if (Environment.isWorker()) {
|
277 | cluster.worker.disconnect();
|
278 | } else {
|
279 | process.exit(1);
|
280 | }
|
281 | });
|
282 |
|
283 | this.app.on('error', function handleError (error) {
|
284 | Logger.error(error.stack);
|
285 | });
|
286 | },
|
287 |
|
288 | |
289 |
|
290 |
|
291 | _isProduction : function () {
|
292 | return this._getEnvironment() === 'production';
|
293 | },
|
294 |
|
295 | |
296 |
|
297 |
|
298 |
|
299 |
|
300 | _loadMiddleware : function (environment) {
|
301 | var self = this,
|
302 | startMiddleware = Date.now(),
|
303 | middlewarePath = path.join(this.options.basePath, 'lib', 'middleware');
|
304 |
|
305 | if (!this.options.middleware || !this.options.middleware[environment]) {
|
306 | return;
|
307 | }
|
308 |
|
309 | this.options.middleware[environment].forEach(function loadFile (file) {
|
310 | var start = Date.now(),
|
311 | middleware = require(path.join(middlewarePath, file));
|
312 | middleware.process(self.app);
|
313 | Logger.profile('Load middleware ' + file, start);
|
314 | });
|
315 |
|
316 | Logger.profile('Load middleware', startMiddleware);
|
317 |
|
318 | return Promise.resolve();
|
319 | },
|
320 |
|
321 | |
322 |
|
323 |
|
324 | _handleMessage : function (message) {
|
325 | switch (message) {
|
326 | case 'shutdown':
|
327 | this._shutdown();
|
328 | break;
|
329 | }
|
330 | },
|
331 |
|
332 | |
333 |
|
334 |
|
335 | _shutdown : function () {
|
336 | if (this.closing === true) {
|
337 |
|
338 | return;
|
339 | }
|
340 |
|
341 | this.closing = true;
|
342 |
|
343 |
|
344 | this.server.close(function () {
|
345 |
|
346 | cluster.worker.disconnect();
|
347 | });
|
348 | }
|
349 | });
|
350 |
|
351 | module.exports = Minor;
|
352 |
|
353 |
|
354 | module.exports.Config = Config;
|
355 | module.exports.Controller = Controller;
|
356 | module.exports.Environment = Environment;
|
357 | module.exports.FileSystem = FileSystem;
|
358 | module.exports.Logger = Logger;
|
359 | module.exports.Path = Path;
|