From 2837edd51f5873b4b8838e7ca1d1f88c9a196778 Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Thu, 6 Feb 2014 01:34:22 -0700 Subject: [PATCH 1/6] chore(cluster): use domain-middleware properly, so domains are used to restart cluster workers --- package.json | 2 +- src/middleware.js | 32 ++++++++++++++++++++++++++++++++ src/server.js | 18 ++---------------- src/utils.js | 38 +------------------------------------- 4 files changed, 36 insertions(+), 54 deletions(-) diff --git a/package.json b/package.json index 172b6e48c9..b39c0cf754 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "pretty-data": "git://github.com/vkiryukhin/pretty-data#master", "js2xmlparser": "~0.1.2", "mongoose": "~3.8.1", - "domain-middleware": "~0.1.0", + "domain-middleware": "git://github.com/HabitRPG/domain-middleware#master", "swagger-node-express": "git://github.com/lefnire/swagger-node-express#habitrpg", "passport": "~0.1.18", "passport-facebook": "~1.0.2", diff --git a/src/middleware.js b/src/middleware.js index edda3917a5..78c754114b 100644 --- a/src/middleware.js +++ b/src/middleware.js @@ -5,8 +5,11 @@ var path = require('path'); var User = require('./models/user').model var limiter = require('connect-ratelimit'); var logging = require('./logging'); +var domainMiddleware = require('domain-middleware'); +var cluster = require('cluster'); module.exports.apiThrottle = function(app) { + if (nconf.get('NODE_ENV') !== 'production') return; app.use(limiter({ end:false, catagories:{ @@ -23,6 +26,35 @@ module.exports.apiThrottle = function(app) { }); } +module.exports.domainMiddleware = function(server,mongoose) { + return domainMiddleware({ + server: { + close:function(){ + server.close(); + mongoose.connection.close(); + } + }, + killTimeout: 10000 + }); +} + +module.exports.errorHandler = function(err, req, res, next) { + //res.locals.domain.emit('error', err); + // when we hit an error, send it to admin as an email. If no ADMIN_EMAIL is present, just send it to yourself (SMTP_USER) + var stack = (err.stack ? err.stack : err.message ? err.message : err) + + "\n ----------------------------\n" + + "\n\noriginalUrl: " + req.originalUrl + + "\n\nauth: " + req.headers['x-api-user'] + ' | ' + req.headers['x-api-key'] + + "\n\nheaders: " + JSON.stringify(req.headers) + + "\n\nbody: " + JSON.stringify(req.body) + + (res.locals.ops ? "\n\ncompleted ops: " + JSON.stringify(res.locals.ops) : ""); + logging.error(stack); + var message = err.message ? err.message : err; + message = (message.length < 200) ? message : message.substring(0,100) + message.substring(message.length-100,message.length); + res.json(500,{err:message}); //res.end(err.message); +} + + module.exports.forceSSL = function(req, res, next){ var baseUrl = nconf.get("BASE_URL"); // Note x-forwarded-proto is used by Heroku & nginx, you'll have to do something different if you're not using those diff --git a/src/server.js b/src/server.js index 04981a8147..c585c3a95f 100644 --- a/src/server.js +++ b/src/server.js @@ -24,7 +24,6 @@ if (cluster.isMaster && (isDev || isProd)) { var express = require("express"); var http = require("http"); var path = require("path"); - var domainMiddleware = require('domain-middleware'); var swagger = require("swagger-node-express"); var middleware = require('./middleware'); @@ -91,14 +90,9 @@ if (cluster.isMaster && (isDev || isProd)) { // ------------ Server Configuration ------------ - domainMiddleware({ - server: server, - killTimeout: 3000 - }), - app.set("port", nconf.get('PORT')); - middleware.apiThrottle(app); + app.use(middleware.domainMiddleware(server,mongoose)); if (!isProd) app.use(express.logger("dev")); app.use(express.compress()); app.set("views", __dirname + "/../views"); @@ -125,11 +119,6 @@ if (cluster.isMaster && (isDev || isProd)) { app.use(express['static'](path.join(__dirname, "/../build"), { maxAge: maxAge })); app.use(express['static'](path.join(__dirname, "/../public"))); - // development only - //if ("development" === app.get("env")) { - // app.use(express.errorHandler()); - //} - // Custom Directives app.use(require('./routes/pages').middleware); app.use(require('./routes/auth').middleware); @@ -137,11 +126,8 @@ if (cluster.isMaster && (isDev || isProd)) { app.use('/api/v2', v2); app.use('/api/v1', require('./routes/apiv1').middleware); app.use('/export', require('./routes/dataexport').middleware); - - app.use(utils.crashWorker(server,mongoose)); - app.use(utils.errorHandler); - require('./routes/apiv2.coffee')(swagger, v2); + app.use(middleware.errorHandler); server.on('request', app); server.listen(app.get("port"), function() { diff --git a/src/utils.js b/src/utils.js index a4a44d52c2..fd29e8f4e8 100644 --- a/src/utils.js +++ b/src/utils.js @@ -2,7 +2,6 @@ var nodemailer = require('nodemailer'); var nconf = require('nconf'); var crypto = require('crypto'); var path = require("path"); -var cluster = require("cluster"); module.exports.sendEmail = function(mailData) { var smtpTransport = nodemailer.createTransport("SMTP",{ @@ -46,39 +45,4 @@ module.exports.setupConfig = function(){ Error.stackTraceLimit = Infinity; if (nconf.get('NODE_ENV') === 'production') require('newrelic'); -}; - -module.exports.crashWorker = function(server,mongoose) { - return function(err, req, res, next) { - if (!cluster.isMaster) { - // make sure we close down within 30 seconds - var killtimer = setTimeout(function() { - process.exit(1); - }, 30000); - // But don't keep the process open just for that! - killtimer.unref(); - // stop taking new requests. - server.close(); - mongoose.connection.close(); - cluster.worker.disconnect(); - } - next(err); - }; -} - - -module.exports.errorHandler = function(err, req, res, next) { - // when we hit an error, send it to admin as an email. If no ADMIN_EMAIL is present, just send it to yourself (SMTP_USER) - var stack = (err.stack ? err.stack : err.message ? err.message : err) + - "\n ----------------------------\n" + - "\n\noriginalUrl: " + req.originalUrl + - "\n\nauth: " + req.headers['x-api-user'] + ' | ' + req.headers['x-api-key'] + - "\n\nheaders: " + JSON.stringify(req.headers) + - "\n\nbody: " + JSON.stringify(req.body) + - (res.locals.ops ? "\n\ncompleted ops: " + JSON.stringify(res.locals.ops) : ""); - var logging = require('./logging'); - logging.error(stack); - var message = err.message ? err.message : err; - message = (message.length < 200) ? message : message.substring(0,100) + message.substring(message.length-100,message.length); - res.json(500,{err:message}); //res.end(err.message); -} +}; \ No newline at end of file From 55c28cbe523fb41dc905b13931234780aa80e78b Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Thu, 6 Feb 2014 11:01:38 -0700 Subject: [PATCH 2/6] chore(domain-middleware): back to real module version --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index b39c0cf754..a529116deb 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,6 @@ "pretty-data": "git://github.com/vkiryukhin/pretty-data#master", "js2xmlparser": "~0.1.2", "mongoose": "~3.8.1", - "domain-middleware": "git://github.com/HabitRPG/domain-middleware#master", "swagger-node-express": "git://github.com/lefnire/swagger-node-express#habitrpg", "passport": "~0.1.18", "passport-facebook": "~1.0.2", @@ -48,7 +47,8 @@ "connect-ratelimit": "0.0.6", "winston": "~0.7.2", "winston-mail": "~0.2.7", - "winston-newrelic": "~0.1.4" + "winston-newrelic": "~0.1.4", + "domain-middleware": "~0.1.0" }, "private": true, "subdomain": "habitrpg", From 66c7f48e2bde48eb4b13908d953c4faae74fe7e0 Mon Sep 17 00:00:00 2001 From: deilann Date: Thu, 6 Feb 2014 11:37:35 -0800 Subject: [PATCH 3/6] updating LFG Updating Looking for Group to the proper (not dead) link. --- views/options/social/tavern.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/options/social/tavern.jade b/views/options/social/tavern.jade index 78a2e5a136..1221da51b4 100644 --- a/views/options/social/tavern.jade +++ b/views/options/social/tavern.jade @@ -27,7 +27,7 @@ table.table.table-striped tr td - a(target='_blank', href='http://habitrpg.wikia.com/wiki/Board:Looking_for_a_Group_(LFG)')=env.t('lfgPosts') + a(target='_blank', href='http://habitrpg.wikia.com/wiki/Board:The_Archery_-_LFG')=env.t('lfgPosts') tr td a(target='_blank', href='https://vimeo.com/57654086')=env.t('tutorial') From f7ded9d6908623c9abf544eb9519e0741ca634ce Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Thu, 6 Feb 2014 13:27:57 -0700 Subject: [PATCH 4/6] chore(logging): remove console.log(isStaticPage) --- src/middleware.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/middleware.js b/src/middleware.js index 78c754114b..236b6807ff 100644 --- a/src/middleware.js +++ b/src/middleware.js @@ -226,7 +226,7 @@ module.exports.locals = function(req, res, next) { if(err) return res.json(500, {err: err}); var isStaticPage = req.url.split('/')[1] === 'static'; // If url contains '/static/' - console.log(isStaticPage) + //console.log(isStaticPage) // Load moment.js language file only when not on static pages language.momentLang = ((!isStaticPage && momentLangs[language.code])|| undefined); From e36d3264bed5a015888f834c967d92f953365e51 Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Thu, 6 Feb 2014 20:08:50 -0700 Subject: [PATCH 5/6] chore(cluster): temporarily disable cluster (revert here) --- src/middleware.js | 14 -------------- src/server.js | 17 +---------------- 2 files changed, 1 insertion(+), 30 deletions(-) diff --git a/src/middleware.js b/src/middleware.js index 236b6807ff..311995fe3f 100644 --- a/src/middleware.js +++ b/src/middleware.js @@ -5,8 +5,6 @@ var path = require('path'); var User = require('./models/user').model var limiter = require('connect-ratelimit'); var logging = require('./logging'); -var domainMiddleware = require('domain-middleware'); -var cluster = require('cluster'); module.exports.apiThrottle = function(app) { if (nconf.get('NODE_ENV') !== 'production') return; @@ -26,18 +24,6 @@ module.exports.apiThrottle = function(app) { }); } -module.exports.domainMiddleware = function(server,mongoose) { - return domainMiddleware({ - server: { - close:function(){ - server.close(); - mongoose.connection.close(); - } - }, - killTimeout: 10000 - }); -} - module.exports.errorHandler = function(err, req, res, next) { //res.locals.domain.emit('error', err); // when we hit an error, send it to admin as an email. If no ADMIN_EMAIL is present, just send it to yourself (SMTP_USER) diff --git a/src/server.js b/src/server.js index c585c3a95f..3f2cb65f3e 100644 --- a/src/server.js +++ b/src/server.js @@ -1,5 +1,4 @@ // Only do the minimal amount of work before forking just in case of a dyno restart -var cluster = require("cluster"); var _ = require('lodash'); var nconf = require('nconf'); var utils = require('./utils'); @@ -8,18 +7,6 @@ var logging = require('./logging'); var isProd = nconf.get('NODE_ENV') === 'production'; var isDev = nconf.get('NODE_ENV') === 'development'; -if (cluster.isMaster && (isDev || isProd)) { - // Fork workers. - _.times(require('os').cpus().length, function(){ - cluster.fork(); - }); - - cluster.on('disconnect', function(worker, code, signal) { - var w = cluster.fork(); // replace the dead worker - logging.info('[%s] [master:%s] worker:%s disconnect! new worker:%s fork', new Date(), process.pid, worker.process.pid, w.process.pid); - }); - -} else { require('coffee-script'); // remove this once we've fully converted over var express = require("express"); var http = require("http"); @@ -92,7 +79,6 @@ if (cluster.isMaster && (isDev || isProd)) { app.set("port", nconf.get('PORT')); middleware.apiThrottle(app); - app.use(middleware.domainMiddleware(server,mongoose)); if (!isProd) app.use(express.logger("dev")); app.use(express.compress()); app.set("views", __dirname + "/../views"); @@ -134,5 +120,4 @@ if (cluster.isMaster && (isDev || isProd)) { return logging.info("Express server listening on port " + app.get("port")); }); - module.exports = server; -} + module.exports = server; \ No newline at end of file From 106a7fa4752ca152c1354a67debd121b48867f15 Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Thu, 6 Feb 2014 12:12:44 -0700 Subject: [PATCH 6/6] refactor(errors): refactor the way errors, offline, & queued ops are handled --- public/js/app.js | 38 +++++++++++++++------- public/js/controllers/notificationCtrl.js | 3 ++ public/js/services/notificationServices.js | 2 +- 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/public/js/app.js b/public/js/app.js index c642aea407..fdffa58ad6 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -200,24 +200,40 @@ window.habitrpg = angular.module('habitrpg', } // Handle errors - var interceptor = ['$rootScope', '$q', function ($rootScope, $q) { + $httpProvider.responseInterceptors.push(['$rootScope', '$q', function ($rootScope, $q) { function success(response) { return response; } function error(response) { - //var status = response.status; - response.data = (response.data.err) ? response.data.err : response.data; - if (response.status == 0) response.data = window.env.t('serverUnreach'); - if (response.status == 500) response.data += window.env.t('seeConsole'); + debugger; + // Offline + if (response.status == 0 || + // don't know why we're getting 404 here, should be 0 + (response.status == 404 && _.isEmpty(response.data))) { + $rootScope.$broadcast('responseText', window.env.t('serverUnreach')); - var error = response.status == 0 ? response.data : (window.env.t('error') + ' ' + response.status + ': ' + response.data); - $rootScope.$broadcast('responseError', error); - console.log(arguments); - return $q.reject(response); + // Needs refresh + } else if (response.needRefresh) { + $rootScope.$broadcast('responseError', "The site has been updated and the page needs to refresh. The last action has not been recorded, please refresh and try again."); + + // 400 range? + } else if (response < 500) { + $rootScope.$broadcast('responseText', response.data.err || response.data); + + // Error + } else { + var error = 'Please reload, ' + + '"'+window.env.t('error')+' '+(response.data.err || response.data || 'something went wrong')+'"' + + window.env.t('seeConsole'); + $rootScope.$broadcast('responseError', error); + console.error(response); + } + + //return $q.reject(response); // this completely halts the chain, meaning we can't queue offline actions + return response; } return function (promise) { return promise.then(success, error); } - }]; - $httpProvider.responseInterceptors.push(interceptor); + }]); }]) diff --git a/public/js/controllers/notificationCtrl.js b/public/js/controllers/notificationCtrl.js index dec5dde0be..5bbdac834f 100644 --- a/public/js/controllers/notificationCtrl.js +++ b/public/js/controllers/notificationCtrl.js @@ -103,5 +103,8 @@ habitrpg.controller('NotificationCtrl', $rootScope.$on('responseError', function(ev, error){ Notification.error(error); }); + $rootScope.$on('responseText', function(ev, error){ + Notification.text(error); + }); } ]); diff --git a/public/js/services/notificationServices.js b/public/js/services/notificationServices.js index 98411901b8..c381976423 100644 --- a/public/js/services/notificationServices.js +++ b/public/js/services/notificationServices.js @@ -10,7 +10,7 @@ angular.module("notificationServices", []) top_offset: 20, align: 'right', //('left', 'right', or 'center') width: 250, //(integer, or 'auto') - delay: 7000, + delay: (type=='error') ? 0 : 7000, allow_dismiss: true, stackup_spacing: 10 // spacing between consecutive stacecked growls. });