From 61c76e4aba1213995af8352250057bb320cd4537 Mon Sep 17 00:00:00 2001 From: akhoury Date: Sun, 7 Feb 2016 13:16:50 -0500 Subject: [PATCH 01/15] add continuation-local-storage support --- package.json | 1 + src/middleware/cls.js | 41 +++++++++++++++++++++++++++++++++++++++++ src/middleware/index.js | 2 ++ 3 files changed, 44 insertions(+) create mode 100644 src/middleware/cls.js diff --git a/package.json b/package.json index b7f7ed676a..0fa110de82 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "connect-mongo": "~1.1.0", "connect-multiparty": "^2.0.0", "connect-redis": "~3.0.2", + "continuation-local-storage": "^3.1.6", "cookie-parser": "^1.3.3", "cron": "^1.0.5", "csurf": "^1.6.1", diff --git a/src/middleware/cls.js b/src/middleware/cls.js new file mode 100644 index 0000000000..31a1065891 --- /dev/null +++ b/src/middleware/cls.js @@ -0,0 +1,41 @@ + +var continuationLocalStorage = require('continuation-local-storage'); + +var NAMESPACE = 'nodebb'; +var namespace = continuationLocalStorage.createNamespace(NAMESPACE); + +var cls = function (req, res, next) { + namespace.run(function() { + var value = {req: req}; + if (process.env.NODE_ENV == 'development') { + value.audit = {created: process.hrtime()}; + } + namespace.set('route', { + req: req, + res: res + }); + next(); + }); +}; + +cls.storage = function () { + return cls.getNamespace(NAMESPACE); +}; + +cls.get = function (key) { + return namespace.get(key); +}; + +cls.set = function (key, value) { + return namespace.set(key, value); +}; + +cls.setItem = cls.set; +cls.getItem = cls.set; +cls.getNamespace = cls.storage; +cls.namespace = namespace; +cls.continuationLocalStorage = continuationLocalStorage; + +module.exports = cls; + + diff --git a/src/middleware/index.js b/src/middleware/index.js index 1cbac02323..3efb1a18f2 100644 --- a/src/middleware/index.js +++ b/src/middleware/index.js @@ -14,6 +14,7 @@ var meta = require('../meta'), compression = require('compression'), favicon = require('serve-favicon'), session = require('express-session'), + cls = require('./cls'), useragent = require('express-useragent'); @@ -73,6 +74,7 @@ module.exports = function(app) { app.use(middleware.addHeaders); app.use(middleware.processRender); + app.use(cls); auth.initialize(app, middleware); return middleware; From 852a1a178e95821d33d877272b992352946e8f17 Mon Sep 17 00:00:00 2001 From: akhoury Date: Sun, 7 Feb 2016 13:34:24 -0500 Subject: [PATCH 02/15] oops --- src/middleware/cls.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/middleware/cls.js b/src/middleware/cls.js index 31a1065891..76a26f9c4f 100644 --- a/src/middleware/cls.js +++ b/src/middleware/cls.js @@ -6,14 +6,12 @@ var namespace = continuationLocalStorage.createNamespace(NAMESPACE); var cls = function (req, res, next) { namespace.run(function() { - var value = {req: req}; + var routeData = {req: req, res: res}; + if (process.env.NODE_ENV == 'development') { - value.audit = {created: process.hrtime()}; + routeData.audit = {created: process.hrtime()}; } - namespace.set('route', { - req: req, - res: res - }); + namespace.set('route', routeData); next(); }); }; From 4245cb2739595557a713e5b94bc9b56e15f041ec Mon Sep 17 00:00:00 2001 From: akhoury Date: Fri, 12 Feb 2016 12:20:21 -0500 Subject: [PATCH 03/15] adding cls support for ws --- src/middleware/cls.js | 27 +++++++++++++++------------ src/middleware/index.js | 2 +- src/socket.io/index.js | 15 +++++++++++---- 3 files changed, 27 insertions(+), 17 deletions(-) diff --git a/src/middleware/cls.js b/src/middleware/cls.js index 76a26f9c4f..16b0e4f848 100644 --- a/src/middleware/cls.js +++ b/src/middleware/cls.js @@ -1,23 +1,26 @@ - +var path = require('path'); var continuationLocalStorage = require('continuation-local-storage'); +var APP_NAMESPACE = require(path.join(__dirname, '../../package.json')).name; +var namespace = continuationLocalStorage.createNamespace(APP_NAMESPACE); -var NAMESPACE = 'nodebb'; -var namespace = continuationLocalStorage.createNamespace(NAMESPACE); +var cls = {}; -var cls = function (req, res, next) { +cls.http = function (req, res, next) { namespace.run(function() { - var routeData = {req: req, res: res}; - - if (process.env.NODE_ENV == 'development') { - routeData.audit = {created: process.hrtime()}; - } - namespace.set('route', routeData); + namespace.set('http', {req: req, res: res}); next(); }); }; -cls.storage = function () { - return cls.getNamespace(NAMESPACE); +cls.socket = function (socket, payload, event, next) { + namespace.run(function() { + namespace.set('ws', { + socket: socket, + payload: payload, + // if it's a '*' event, then we grab it from the payload + event: event || ((payload || {}).data || [])[0]}); + next(); + }); }; cls.get = function (key) { diff --git a/src/middleware/index.js b/src/middleware/index.js index 3efb1a18f2..f7e41b8f9b 100644 --- a/src/middleware/index.js +++ b/src/middleware/index.js @@ -74,7 +74,7 @@ module.exports = function(app) { app.use(middleware.addHeaders); app.use(middleware.processRender); - app.use(cls); + app.use(cls.http); auth.initialize(app, middleware); return middleware; diff --git a/src/socket.io/index.js b/src/socket.io/index.js index 826e5db452..4e91eb9ca6 100644 --- a/src/socket.io/index.js +++ b/src/socket.io/index.js @@ -11,6 +11,7 @@ var SocketIO = require('socket.io'), user = require('../user'), logger = require('../logger'), ratelimit = require('../middleware/ratelimit'), + cls = require('../middleware/cls'), Sockets = {}, Namespaces = {}; @@ -43,14 +44,20 @@ function onConnection(socket) { logger.io_one(socket, socket.uid); - onConnect(socket); + cls.socket(socket, null, 'connection', function () { + onConnect(socket); + }); - socket.on('disconnect', function(data) { - onDisconnect(socket, data); + socket.on('disconnect', function(payload) { + cls.socket(socket, payload, 'disconnect', function () { + onDisconnect(socket, payload); + }); }); socket.on('*', function(payload) { - onMessage(socket, payload); + cls.socket(socket, payload, null, function() { + onMessage(socket, payload); + }); }); } From 496e5ae8bfe9c8abf55b3a37bde9bdb05662cad5 Mon Sep 17 00:00:00 2001 From: akhoury Date: Fri, 12 Feb 2016 12:30:39 -0500 Subject: [PATCH 04/15] comment --- src/middleware/cls.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/middleware/cls.js b/src/middleware/cls.js index 16b0e4f848..ce3d79b922 100644 --- a/src/middleware/cls.js +++ b/src/middleware/cls.js @@ -17,7 +17,7 @@ cls.socket = function (socket, payload, event, next) { namespace.set('ws', { socket: socket, payload: payload, - // if it's a '*' event, then we grab it from the payload + // if it's a null event, then we grab it from the payload event: event || ((payload || {}).data || [])[0]}); next(); }); From f47c06279ae1a79a9acbe432b8e6de76e3f6dd53 Mon Sep 17 00:00:00 2001 From: akhoury Date: Tue, 16 Feb 2016 23:07:36 -0500 Subject: [PATCH 05/15] added depracation warning --- src/plugins/hooks.js | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/plugins/hooks.js b/src/plugins/hooks.js index 3330608154..7148654eeb 100644 --- a/src/plugins/hooks.js +++ b/src/plugins/hooks.js @@ -10,6 +10,9 @@ module.exports = function(Plugins) { 'action:user.loggedOut': 'static:user.loggedOut' }; + // todo: remove when breaking all hooks params by removing req/res/socket/uid + Plugins.clsDeprecatedParamsWarnedHooks = {}; + /* `data` is an object consisting of (* is required): `data.hook`*, the name of the NodeBB hook @@ -29,7 +32,7 @@ module.exports = function(Plugins) { var method; if (Object.keys(Plugins.deprecatedHooks).indexOf(data.hook) !== -1) { - winston.warn('[plugins/' + id + '] Hook `' + data.hook + '` is deprecated, ' + + winston.warn('[plugins/' + id + '] Hook `' + data.hook + '` is deprecated, ' + (Plugins.deprecatedHooks[data.hook] ? 'please use `' + Plugins.deprecatedHooks[data.hook] + '` instead.' : 'there is no alternative.' @@ -71,6 +74,20 @@ module.exports = function(Plugins) { var hookList = Plugins.loadedHooks[hook]; var hookType = hook.split(':')[0]; + // todo: remove when breaking all hooks params by removing req/res/socket/uid + if (!Plugins.clsDeprecatedParamsWarnedHooks[hook] + && params + && Array.isArray(hookList) + && hookList.length + && (params.req || params.res || params.socket || params.uid)) { + + Plugins.clsDeprecatedParamsWarnedHooks[hook] = true; + + winston.warn('[plugins] hook `' + hook + '` \'s `params.req`, `params.res`, `params.uid` and `params.socket` are being deprecated, ' + + 'plugins should use the `middleware/cls` module instead to get a reference to the http-req/res and socket (which you can get the current `uid`) ' + + '- for more info, visit https://docs.nodebb.org/en/latest/plugins/create.html#getting-a-reference-to-req-res-socket-and-uid-within-any-plugin-hook'); + } + switch (hookType) { case 'filter': fireFilterHook(hook, hookList, params, callback); From a8411d44fdc7bd143ec34a73e98caff0584bf4b4 Mon Sep 17 00:00:00 2001 From: akhoury Date: Sun, 28 Feb 2016 14:52:51 -0500 Subject: [PATCH 06/15] merge --- src/socket.io/index.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/socket.io/index.js b/src/socket.io/index.js index 8f176e5241..de504ab523 100644 --- a/src/socket.io/index.js +++ b/src/socket.io/index.js @@ -1,11 +1,5 @@ "use strict"; -var SocketIO = require('socket.io'), - socketioWildcard = require('socketio-wildcard')(), - async = require('async'), - nconf = require('nconf'), - cookieParser = require('cookie-parser')(nconf.get('secret')), - winston = require('winston'), var SocketIO = require('socket.io'); var socketioWildcard = require('socketio-wildcard')(); var async = require('async'); From 509676fdf40d17a6867418d7d6ad176e4f0d028e Mon Sep 17 00:00:00 2001 From: akhoury Date: Sun, 28 Feb 2016 15:24:31 -0500 Subject: [PATCH 07/15] add deprecation warnings for CLS --- src/plugins/hooks.js | 48 +++++++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/src/plugins/hooks.js b/src/plugins/hooks.js index 7148654eeb..04691bce71 100644 --- a/src/plugins/hooks.js +++ b/src/plugins/hooks.js @@ -10,8 +10,27 @@ module.exports = function(Plugins) { 'action:user.loggedOut': 'static:user.loggedOut' }; - // todo: remove when breaking all hooks params by removing req/res/socket/uid - Plugins.clsDeprecatedParamsWarnedHooks = {}; + Plugins.deprecatedHooksParams = { + 'action:homepage.get': '{req, res}', + 'filter:register.check': '{req, res}', + 'action:user.loggedOut': '{req, res}', + 'static:user.loggedOut': '{req, res}', + 'filter:categories.build': '{req, res}', + 'filter:category.build': '{req, res}', + 'filter:group.build': '{req, res}', + 'filter:register.build': '{req, res}', + 'filter:composer.build': '{req, res}', + 'filter:popular.build': '{req, res}', + 'filter:recent.build': '{req, res}', + 'filter:topic.build': '{req, res}', + 'filter:users.build': '{req, res}', + 'filter:admin.category.get': '{req, res}', + 'filter:middleware.renderHeader': '{req, res}', + 'filter:widget.render': '{req, res}', + 'filter:middleware.buildHeader': '{req, locals}', + 'action:middleware.pageView': '{req}', + 'action:meta.override404': '{req}' + }; /* `data` is an object consisting of (* is required): @@ -38,6 +57,17 @@ module.exports = function(Plugins) { 'there is no alternative.' ) ); + } else { + // handle hook's startsWith, i.e. action:homepage.get + var _parts = data.hook.split(':'); + _parts.pop(); + var _hook = _parts.join(':'); + if (Plugins.deprecatedHooksParams[_hook]) { + winston.warn('[plugins/' + id + '] Hook `' + _hook + '` parameters: `' + Plugins.deprecatedHooksParams[_hook] + '`, are being deprecated, ' + + 'all plugins should now use the `middleware/cls` module instead of hook\'s arguments to get a reference to the `req`, `res` and/or `socket` object(s) (from which you can get the current `uid` if you need to.) ' + + '- for more info, visit https://docs.nodebb.org/en/latest/plugins/create.html#getting-a-reference-to-req-res-socket-and-uid-within-any-plugin-hook') + delete Plugins.deprecatedHooksParams[_hook]; + } } if (data.hook && data.method) { @@ -74,20 +104,6 @@ module.exports = function(Plugins) { var hookList = Plugins.loadedHooks[hook]; var hookType = hook.split(':')[0]; - // todo: remove when breaking all hooks params by removing req/res/socket/uid - if (!Plugins.clsDeprecatedParamsWarnedHooks[hook] - && params - && Array.isArray(hookList) - && hookList.length - && (params.req || params.res || params.socket || params.uid)) { - - Plugins.clsDeprecatedParamsWarnedHooks[hook] = true; - - winston.warn('[plugins] hook `' + hook + '` \'s `params.req`, `params.res`, `params.uid` and `params.socket` are being deprecated, ' - + 'plugins should use the `middleware/cls` module instead to get a reference to the http-req/res and socket (which you can get the current `uid`) ' - + '- for more info, visit https://docs.nodebb.org/en/latest/plugins/create.html#getting-a-reference-to-req-res-socket-and-uid-within-any-plugin-hook'); - } - switch (hookType) { case 'filter': fireFilterHook(hook, hookList, params, callback); From 53e96270012fe3ea0a6cb605ffdfcb0b94ce25f2 Mon Sep 17 00:00:00 2001 From: Aziz Khoury Date: Fri, 15 Apr 2016 15:55:55 -0400 Subject: [PATCH 08/15] unify request as a store key for both http and websockets calls --- src/middleware/cls.js | 11 ++++------- src/socket.io/index.js | 7 +++++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/middleware/cls.js b/src/middleware/cls.js index ce3d79b922..ce795eae6a 100644 --- a/src/middleware/cls.js +++ b/src/middleware/cls.js @@ -1,4 +1,6 @@ var path = require('path'); +var sockets = require('path'); +var websockets = require('../socket.io'); var continuationLocalStorage = require('continuation-local-storage'); var APP_NAMESPACE = require(path.join(__dirname, '../../package.json')).name; var namespace = continuationLocalStorage.createNamespace(APP_NAMESPACE); @@ -7,18 +9,14 @@ var cls = {}; cls.http = function (req, res, next) { namespace.run(function() { - namespace.set('http', {req: req, res: res}); + namespace.set('request', req); next(); }); }; cls.socket = function (socket, payload, event, next) { namespace.run(function() { - namespace.set('ws', { - socket: socket, - payload: payload, - // if it's a null event, then we grab it from the payload - event: event || ((payload || {}).data || [])[0]}); + namespace.set('request', websockets.reqFromSocket(socket, payload, event)); next(); }); }; @@ -33,7 +31,6 @@ cls.set = function (key, value) { cls.setItem = cls.set; cls.getItem = cls.set; -cls.getNamespace = cls.storage; cls.namespace = namespace; cls.continuationLocalStorage = continuationLocalStorage; diff --git a/src/socket.io/index.js b/src/socket.io/index.js index de504ab523..76fda883a6 100644 --- a/src/socket.io/index.js +++ b/src/socket.io/index.js @@ -248,7 +248,7 @@ Sockets.getOnlineAnonCount = function () { return room ? room.length : 0; }; -Sockets.reqFromSocket = function(socket) { +Sockets.reqFromSocket = function(socket, payload, event) { var headers = socket.request.headers; var host = headers.host; var referer = headers.referer || ''; @@ -256,11 +256,14 @@ Sockets.reqFromSocket = function(socket) { return { ip: headers['x-forwarded-for'] || socket.ip, host: host, + uid: socket.uid, protocol: socket.request.connection.encrypted ? 'https' : 'http', secure: !!socket.request.connection.encrypted, url: referer, + body: {event: event || ((payload || {}).data || [])[0], payload: payload}, path: referer.substr(referer.indexOf(host) + host.length), - headers: headers + headers: headers, + _socket: socket }; }; From 4f3a962f7fe3636094c4b5062ca6236e3448453f Mon Sep 17 00:00:00 2001 From: Aziz Khoury Date: Fri, 15 Apr 2016 16:42:22 -0400 Subject: [PATCH 09/15] what did i do? --- src/socket.io/index.js | 336 ++++++++++++++++++++--------------------- 1 file changed, 160 insertions(+), 176 deletions(-) diff --git a/src/socket.io/index.js b/src/socket.io/index.js index 4092b576bf..809f21ea11 100644 --- a/src/socket.io/index.js +++ b/src/socket.io/index.js @@ -8,227 +8,211 @@ var cookieParser = require('cookie-parser')(nconf.get('secret')); var winston = require('winston'); var db = require('../database'); -var user = require('../user'); var logger = require('../logger'); var ratelimit = require('../middleware/ratelimit'); -var cls = require('../middleware/cls'); -var io; +var Sockets = {}; +var Namespaces = {}; -(function(Sockets) { - var Namespaces = {}; +var io; - Sockets.init = function(server) { - requireModules(); +Sockets.init = function(server) { + requireModules(); - io = new SocketIO({ - path: nconf.get('relative_path') + '/socket.io' - }); + io = new SocketIO({ + path: nconf.get('relative_path') + '/socket.io' + }); - addRedisAdapter(io); + addRedisAdapter(io); - io.use(socketioWildcard); - io.use(authorize); + io.use(socketioWildcard); + io.use(authorize); - io.on('connection', onConnection); + io.on('connection', onConnection); - io.on('disconnect', function(data) { - onDisconnect(io, data); - }); + io.listen(server, { + transports: nconf.get('socket.io:transports') + }); - io.listen(server, { - transports: nconf.get('socket.io:transports') - }); + Sockets.server = io; +}; - Sockets.server = io; - }; +function onConnection(socket) { + socket.ip = socket.request.headers['x-forwarded-for'] || socket.request.connection.remoteAddress; - function onConnection(socket) { - socket.ip = socket.request.headers['x-forwarded-for'] || socket.request.connection.remoteAddress; + logger.io_one(socket, socket.uid); - logger.io_one(socket, socket.uid); + onConnect(socket); - cls.socket(socket, null, 'connection', function () { - onConnect(socket); - }); + socket.on('*', function(payload) { + onMessage(socket, payload); + }); +} - socket.on('*', function(payload) { - cls.socket(socket, payload, null, function() { - onMessage(socket, payload); - }); - }); +function onConnect(socket) { + if (socket.uid) { + socket.join('uid_' + socket.uid); + socket.join('online_users'); + } else { + socket.join('online_guests'); } +} - function onConnect(socket) { - if (socket.uid) { - socket.join('uid_' + socket.uid); - socket.join('online_users'); - } else { - socket.join('online_guests'); - } + +function onMessage(socket, payload) { + if (!payload.data.length) { + return winston.warn('[socket.io] Empty payload'); } - function onDisconnect(socket) { - cls.socket(socket, null, 'disconnect', function() {}); + var eventName = payload.data[0]; + var params = payload.data[1]; + var callback = typeof payload.data[payload.data.length - 1] === 'function' ? payload.data[payload.data.length - 1] : function() {}; + + if (!eventName) { + return winston.warn('[socket.io] Empty method name'); } + var parts = eventName.toString().split('.'); + var namespace = parts[0]; + var methodToCall = parts.reduce(function(prev, cur) { + if (prev !== null && prev[cur]) { + return prev[cur]; + } else { + return null; + } + }, Namespaces); - function onMessage(socket, payload) { - if (!payload.data.length) { - return winston.warn('[socket.io] Empty payload'); + if(!methodToCall) { + if (process.env.NODE_ENV === 'development') { + winston.warn('[socket.io] Unrecognized message: ' + eventName); } + return; + } - var eventName = payload.data[0]; - var params = payload.data[1]; - var callback = typeof payload.data[payload.data.length - 1] === 'function' ? payload.data[payload.data.length - 1] : function() {}; + socket.previousEvents = socket.previousEvents || []; + socket.previousEvents.push(eventName); + if (socket.previousEvents.length > 20) { + socket.previousEvents.shift(); + } - if (!eventName) { - return winston.warn('[socket.io] Empty method name'); - } + if (!eventName.startsWith('admin.') && ratelimit.isFlooding(socket)) { + winston.warn('[socket.io] Too many emits! Disconnecting uid : ' + socket.uid + '. Events : ' + socket.previousEvents); + return socket.disconnect(); + } - var parts = eventName.toString().split('.'); - var namespace = parts[0]; - var methodToCall = parts.reduce(function(prev, cur) { - if (prev !== null && prev[cur]) { - return prev[cur]; + async.waterfall([ + function (next) { + validateSession(socket, next); + }, + function (next) { + if (Namespaces[namespace].before) { + Namespaces[namespace].before(socket, eventName, params, next); } else { - return null; - } - }, Namespaces); - - if(!methodToCall) { - if (process.env.NODE_ENV === 'development') { - winston.warn('[socket.io] Unrecognized message: ' + eventName); + next(); } - return; - } - - socket.previousEvents = socket.previousEvents || []; - socket.previousEvents.push(eventName); - if (socket.previousEvents.length > 20) { - socket.previousEvents.shift(); + }, + function (next) { + methodToCall(socket, params, next); } - - if (!eventName.startsWith('admin.') && ratelimit.isFlooding(socket)) { - winston.warn('[socket.io] Too many emits! Disconnecting uid : ' + socket.uid + '. Events : ' + socket.previousEvents); - return socket.disconnect(); + ], function(err, result) { + callback(err ? {message: err.message} : null, result); + }); +} + +function requireModules() { + var modules = ['admin', 'categories', 'groups', 'meta', 'modules', + 'notifications', 'plugins', 'posts', 'topics', 'user', 'blacklist' + ]; + + modules.forEach(function(module) { + Namespaces[module] = require('./' + module); + }); +} + +function validateSession(socket, callback) { + var req = socket.request; + if (!req.signedCookies || !req.signedCookies['express.sid']) { + return callback(new Error('[[error:invalid-session]]')); + } + db.sessionStore.get(req.signedCookies['express.sid'], function(err, sessionData) { + if (err || !sessionData) { + return callback(err || new Error('[[error:invalid-session]]')); } - async.waterfall([ - function (next) { - validateSession(socket, next); - }, - function (next) { - if (Namespaces[namespace].before) { - Namespaces[namespace].before(socket, eventName, params, next); - } else { - next(); - } - }, - function (next) { - methodToCall(socket, params, next); - } - ], function(err, result) { - callback(err ? {message: err.message} : null, result); - }); - } + callback(); + }); +} - function requireModules() { - var modules = ['admin', 'categories', 'groups', 'meta', 'modules', - 'notifications', 'plugins', 'posts', 'topics', 'user', 'blacklist' - ]; +function authorize(socket, callback) { + var request = socket.request; - modules.forEach(function(module) { - Namespaces[module] = require('./' + module); - }); + if (!request) { + return callback(new Error('[[error:not-authorized]]')); } - function validateSession(socket, callback) { - var req = socket.request; - if (!req.signedCookies || !req.signedCookies['express.sid']) { - return callback(new Error('[[error:invalid-session]]')); + async.waterfall([ + function(next) { + cookieParser(request, {}, next); + }, + function(next) { + db.sessionStore.get(request.signedCookies['express.sid'], function(err, sessionData) { + if (err) { + return next(err); + } + if (sessionData && sessionData.passport && sessionData.passport.user) { + request.session = sessionData; + socket.uid = parseInt(sessionData.passport.user, 10); + } else { + socket.uid = 0; + } + next(); + }); } - db.sessionStore.get(req.signedCookies['express.sid'], function(err, sessionData) { - if (err || !sessionData) { - return callback(err || new Error('[[error:invalid-session]]')); - } - - callback(); - }); + ], callback); +} + +function addRedisAdapter(io) { + if (nconf.get('redis')) { + var redisAdapter = require('socket.io-redis'); + var redis = require('../database/redis'); + var pub = redis.connect({return_buffers: true}); + var sub = redis.connect({return_buffers: true}); + + io.adapter(redisAdapter({pubClient: pub, subClient: sub})); + } else if (nconf.get('isCluster') === 'true') { + winston.warn('[socket.io] Clustering detected, you are advised to configure Redis as a websocket store.'); } +} - function authorize(socket, callback) { - var request = socket.request; +Sockets.in = function(room) { + return io.in(room); +}; - if (!request) { - return callback(new Error('[[error:not-authorized]]')); - } - - async.waterfall([ - function(next) { - cookieParser(request, {}, next); - }, - function(next) { - db.sessionStore.get(request.signedCookies['express.sid'], function(err, sessionData) { - if (err) { - return next(err); - } - if (sessionData && sessionData.passport && sessionData.passport.user) { - request.session = sessionData; - socket.uid = parseInt(sessionData.passport.user, 10); - } else { - socket.uid = 0; - } - next(); - }); - } - ], callback); +Sockets.getUserSocketCount = function(uid) { + if (!io) { + return 0; } - function addRedisAdapter(io) { - if (nconf.get('redis')) { - var redisAdapter = require('socket.io-redis'); - var redis = require('../database/redis'); - var pub = redis.connect({return_buffers: true}); - var sub = redis.connect({return_buffers: true}); - - io.adapter(redisAdapter({pubClient: pub, subClient: sub})); - } else if (nconf.get('isCluster') === 'true') { - winston.warn('[socket.io] Clustering detected, you are advised to configure Redis as a websocket store.'); - } - } + var room = io.sockets.adapter.rooms['uid_' + uid]; + return room ? room.length : 0; +}; - Sockets.in = function(room) { - return io.in(room); - }; - Sockets.getUserSocketCount = function(uid) { - if (!io) { - return 0; - } +Sockets.reqFromSocket = function(socket) { + var headers = socket.request.headers; + var host = headers.host; + var referer = headers.referer || ''; - var room = io.sockets.adapter.rooms['uid_' + uid]; - return room ? room.length : 0; + return { + ip: headers['x-forwarded-for'] || socket.ip, + host: host, + protocol: socket.request.connection.encrypted ? 'https' : 'http', + secure: !!socket.request.connection.encrypted, + url: referer, + path: referer.substr(referer.indexOf(host) + host.length), + headers: headers }; +}; - Sockets.reqFromSocket = function(socket, payload, event) { - var headers = socket.request.headers; - var host = headers.host; - var referer = headers.referer || ''; - - return { - ip: headers['x-forwarded-for'] || socket.ip, - host: host, - uid: socket.uid, - protocol: socket.request.connection.encrypted ? 'https' : 'http', - secure: !!socket.request.connection.encrypted, - url: referer, - body: {event: event || ((payload || {}).data || [])[0], payload: payload}, - path: referer.substr(referer.indexOf(host) + host.length), - headers: headers, - _socket: socket - }; - }; - -})(exports); \ No newline at end of file +module.exports = Sockets; \ No newline at end of file From 807e3a9d7e28e92249713653db32cb34b5850b09 Mon Sep 17 00:00:00 2001 From: Aziz Khoury Date: Fri, 15 Apr 2016 16:45:04 -0400 Subject: [PATCH 10/15] adding cls back in socketio index --- src/socket.io/index.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/socket.io/index.js b/src/socket.io/index.js index 809f21ea11..419b083478 100644 --- a/src/socket.io/index.js +++ b/src/socket.io/index.js @@ -10,6 +10,7 @@ var winston = require('winston'); var db = require('../database'); var logger = require('../logger'); var ratelimit = require('../middleware/ratelimit'); +var cls = require('../middleware/cls'); var Sockets = {}; var Namespaces = {}; @@ -29,6 +30,7 @@ Sockets.init = function(server) { io.use(authorize); io.on('connection', onConnection); + io.on('disconnect', onDisconnect); io.listen(server, { transports: nconf.get('socket.io:transports') @@ -42,10 +44,14 @@ function onConnection(socket) { logger.io_one(socket, socket.uid); - onConnect(socket); + cls.socket(socket, null, 'connection', function() { + onConnect(socket); + }); socket.on('*', function(payload) { - onMessage(socket, payload); + cls.socket(socket, payload, null, function() { + onMessage(socket, payload); + }); }); } @@ -58,6 +64,10 @@ function onConnect(socket) { } } +function onDisconnect(socket) { + cls.socket(socket, null, 'disconnect', function() {}); +} + function onMessage(socket, payload) { if (!payload.data.length) { From c07e29bad6b1f7b568ad9c87cf4d90e4dfc71f8b Mon Sep 17 00:00:00 2001 From: Aziz Khoury Date: Fri, 15 Apr 2016 16:47:55 -0400 Subject: [PATCH 11/15] fix circular dependency -- involves indentations fix --- src/socket.io/index.js | 340 ++++++++++++++++++++--------------------- 1 file changed, 170 insertions(+), 170 deletions(-) diff --git a/src/socket.io/index.js b/src/socket.io/index.js index 419b083478..e417f6d250 100644 --- a/src/socket.io/index.js +++ b/src/socket.io/index.js @@ -12,217 +12,217 @@ var logger = require('../logger'); var ratelimit = require('../middleware/ratelimit'); var cls = require('../middleware/cls'); -var Sockets = {}; -var Namespaces = {}; +(function(Sockets) { + var Namespaces = {}; + var io; -var io; + Sockets.init = function (server) { + requireModules(); -Sockets.init = function(server) { - requireModules(); - - io = new SocketIO({ - path: nconf.get('relative_path') + '/socket.io' - }); + io = new SocketIO({ + path: nconf.get('relative_path') + '/socket.io' + }); - addRedisAdapter(io); + addRedisAdapter(io); - io.use(socketioWildcard); - io.use(authorize); + io.use(socketioWildcard); + io.use(authorize); - io.on('connection', onConnection); - io.on('disconnect', onDisconnect); + io.on('connection', onConnection); + io.on('disconnect', onDisconnect); - io.listen(server, { - transports: nconf.get('socket.io:transports') - }); + io.listen(server, { + transports: nconf.get('socket.io:transports') + }); - Sockets.server = io; -}; + Sockets.server = io; + }; -function onConnection(socket) { - socket.ip = socket.request.headers['x-forwarded-for'] || socket.request.connection.remoteAddress; + function onConnection(socket) { + socket.ip = socket.request.headers['x-forwarded-for'] || socket.request.connection.remoteAddress; - logger.io_one(socket, socket.uid); + logger.io_one(socket, socket.uid); - cls.socket(socket, null, 'connection', function() { - onConnect(socket); - }); + cls.socket(socket, null, 'connection', function () { + onConnect(socket); + }); - socket.on('*', function(payload) { - cls.socket(socket, payload, null, function() { - onMessage(socket, payload); + socket.on('*', function (payload) { + cls.socket(socket, payload, null, function () { + onMessage(socket, payload); + }); }); - }); -} - -function onConnect(socket) { - if (socket.uid) { - socket.join('uid_' + socket.uid); - socket.join('online_users'); - } else { - socket.join('online_guests'); } -} -function onDisconnect(socket) { - cls.socket(socket, null, 'disconnect', function() {}); -} - - -function onMessage(socket, payload) { - if (!payload.data.length) { - return winston.warn('[socket.io] Empty payload'); + function onConnect(socket) { + if (socket.uid) { + socket.join('uid_' + socket.uid); + socket.join('online_users'); + } else { + socket.join('online_guests'); + } } - var eventName = payload.data[0]; - var params = payload.data[1]; - var callback = typeof payload.data[payload.data.length - 1] === 'function' ? payload.data[payload.data.length - 1] : function() {}; - - if (!eventName) { - return winston.warn('[socket.io] Empty method name'); + function onDisconnect(socket) { + cls.socket(socket, null, 'disconnect', function () { + }); } - var parts = eventName.toString().split('.'); - var namespace = parts[0]; - var methodToCall = parts.reduce(function(prev, cur) { - if (prev !== null && prev[cur]) { - return prev[cur]; - } else { - return null; - } - }, Namespaces); - if(!methodToCall) { - if (process.env.NODE_ENV === 'development') { - winston.warn('[socket.io] Unrecognized message: ' + eventName); + function onMessage(socket, payload) { + if (!payload.data.length) { + return winston.warn('[socket.io] Empty payload'); } - return; - } - socket.previousEvents = socket.previousEvents || []; - socket.previousEvents.push(eventName); - if (socket.previousEvents.length > 20) { - socket.previousEvents.shift(); - } + var eventName = payload.data[0]; + var params = payload.data[1]; + var callback = typeof payload.data[payload.data.length - 1] === 'function' ? payload.data[payload.data.length - 1] : function () { + }; - if (!eventName.startsWith('admin.') && ratelimit.isFlooding(socket)) { - winston.warn('[socket.io] Too many emits! Disconnecting uid : ' + socket.uid + '. Events : ' + socket.previousEvents); - return socket.disconnect(); - } + if (!eventName) { + return winston.warn('[socket.io] Empty method name'); + } - async.waterfall([ - function (next) { - validateSession(socket, next); - }, - function (next) { - if (Namespaces[namespace].before) { - Namespaces[namespace].before(socket, eventName, params, next); + var parts = eventName.toString().split('.'); + var namespace = parts[0]; + var methodToCall = parts.reduce(function (prev, cur) { + if (prev !== null && prev[cur]) { + return prev[cur]; } else { - next(); + return null; } - }, - function (next) { - methodToCall(socket, params, next); + }, Namespaces); + + if (!methodToCall) { + if (process.env.NODE_ENV === 'development') { + winston.warn('[socket.io] Unrecognized message: ' + eventName); + } + return; } - ], function(err, result) { - callback(err ? {message: err.message} : null, result); - }); -} - -function requireModules() { - var modules = ['admin', 'categories', 'groups', 'meta', 'modules', - 'notifications', 'plugins', 'posts', 'topics', 'user', 'blacklist' - ]; - - modules.forEach(function(module) { - Namespaces[module] = require('./' + module); - }); -} - -function validateSession(socket, callback) { - var req = socket.request; - if (!req.signedCookies || !req.signedCookies['express.sid']) { - return callback(new Error('[[error:invalid-session]]')); - } - db.sessionStore.get(req.signedCookies['express.sid'], function(err, sessionData) { - if (err || !sessionData) { - return callback(err || new Error('[[error:invalid-session]]')); + + socket.previousEvents = socket.previousEvents || []; + socket.previousEvents.push(eventName); + if (socket.previousEvents.length > 20) { + socket.previousEvents.shift(); } - callback(); - }); -} + if (!eventName.startsWith('admin.') && ratelimit.isFlooding(socket)) { + winston.warn('[socket.io] Too many emits! Disconnecting uid : ' + socket.uid + '. Events : ' + socket.previousEvents); + return socket.disconnect(); + } -function authorize(socket, callback) { - var request = socket.request; + async.waterfall([ + function (next) { + validateSession(socket, next); + }, + function (next) { + if (Namespaces[namespace].before) { + Namespaces[namespace].before(socket, eventName, params, next); + } else { + next(); + } + }, + function (next) { + methodToCall(socket, params, next); + } + ], function (err, result) { + callback(err ? {message: err.message} : null, result); + }); + } + + function requireModules() { + var modules = ['admin', 'categories', 'groups', 'meta', 'modules', + 'notifications', 'plugins', 'posts', 'topics', 'user', 'blacklist' + ]; - if (!request) { - return callback(new Error('[[error:not-authorized]]')); + modules.forEach(function (module) { + Namespaces[module] = require('./' + module); + }); } - async.waterfall([ - function(next) { - cookieParser(request, {}, next); - }, - function(next) { - db.sessionStore.get(request.signedCookies['express.sid'], function(err, sessionData) { - if (err) { - return next(err); - } - if (sessionData && sessionData.passport && sessionData.passport.user) { - request.session = sessionData; - socket.uid = parseInt(sessionData.passport.user, 10); - } else { - socket.uid = 0; - } - next(); - }); + function validateSession(socket, callback) { + var req = socket.request; + if (!req.signedCookies || !req.signedCookies['express.sid']) { + return callback(new Error('[[error:invalid-session]]')); } - ], callback); -} - -function addRedisAdapter(io) { - if (nconf.get('redis')) { - var redisAdapter = require('socket.io-redis'); - var redis = require('../database/redis'); - var pub = redis.connect({return_buffers: true}); - var sub = redis.connect({return_buffers: true}); - - io.adapter(redisAdapter({pubClient: pub, subClient: sub})); - } else if (nconf.get('isCluster') === 'true') { - winston.warn('[socket.io] Clustering detected, you are advised to configure Redis as a websocket store.'); + db.sessionStore.get(req.signedCookies['express.sid'], function (err, sessionData) { + if (err || !sessionData) { + return callback(err || new Error('[[error:invalid-session]]')); + } + + callback(); + }); } -} -Sockets.in = function(room) { - return io.in(room); -}; + function authorize(socket, callback) { + var request = socket.request; + + if (!request) { + return callback(new Error('[[error:not-authorized]]')); + } -Sockets.getUserSocketCount = function(uid) { - if (!io) { - return 0; + async.waterfall([ + function (next) { + cookieParser(request, {}, next); + }, + function (next) { + db.sessionStore.get(request.signedCookies['express.sid'], function (err, sessionData) { + if (err) { + return next(err); + } + if (sessionData && sessionData.passport && sessionData.passport.user) { + request.session = sessionData; + socket.uid = parseInt(sessionData.passport.user, 10); + } else { + socket.uid = 0; + } + next(); + }); + } + ], callback); } - var room = io.sockets.adapter.rooms['uid_' + uid]; - return room ? room.length : 0; -}; + function addRedisAdapter(io) { + if (nconf.get('redis')) { + var redisAdapter = require('socket.io-redis'); + var redis = require('../database/redis'); + var pub = redis.connect({return_buffers: true}); + var sub = redis.connect({return_buffers: true}); + io.adapter(redisAdapter({pubClient: pub, subClient: sub})); + } else if (nconf.get('isCluster') === 'true') { + winston.warn('[socket.io] Clustering detected, you are advised to configure Redis as a websocket store.'); + } + } -Sockets.reqFromSocket = function(socket) { - var headers = socket.request.headers; - var host = headers.host; - var referer = headers.referer || ''; + Sockets.in = function (room) { + return io.in(room); + }; + + Sockets.getUserSocketCount = function (uid) { + if (!io) { + return 0; + } - return { - ip: headers['x-forwarded-for'] || socket.ip, - host: host, - protocol: socket.request.connection.encrypted ? 'https' : 'http', - secure: !!socket.request.connection.encrypted, - url: referer, - path: referer.substr(referer.indexOf(host) + host.length), - headers: headers + var room = io.sockets.adapter.rooms['uid_' + uid]; + return room ? room.length : 0; }; -}; -module.exports = Sockets; \ No newline at end of file + Sockets.reqFromSocket = function (socket) { + var headers = socket.request.headers; + var host = headers.host; + var referer = headers.referer || ''; + + return { + ip: headers['x-forwarded-for'] || socket.ip, + host: host, + protocol: socket.request.connection.encrypted ? 'https' : 'http', + secure: !!socket.request.connection.encrypted, + url: referer, + path: referer.substr(referer.indexOf(host) + host.length), + headers: headers + }; + }; + +})(exports); \ No newline at end of file From 02e53fd4428f3861cbe422ba9955e045cb1fbc78 Mon Sep 17 00:00:00 2001 From: Aziz Khoury Date: Fri, 15 Apr 2016 16:59:25 -0400 Subject: [PATCH 12/15] update deprecation message --- src/plugins/hooks.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/hooks.js b/src/plugins/hooks.js index 04691bce71..c7d72f7d3e 100644 --- a/src/plugins/hooks.js +++ b/src/plugins/hooks.js @@ -65,7 +65,7 @@ module.exports = function(Plugins) { if (Plugins.deprecatedHooksParams[_hook]) { winston.warn('[plugins/' + id + '] Hook `' + _hook + '` parameters: `' + Plugins.deprecatedHooksParams[_hook] + '`, are being deprecated, ' + 'all plugins should now use the `middleware/cls` module instead of hook\'s arguments to get a reference to the `req`, `res` and/or `socket` object(s) (from which you can get the current `uid` if you need to.) ' - + '- for more info, visit https://docs.nodebb.org/en/latest/plugins/create.html#getting-a-reference-to-req-res-socket-and-uid-within-any-plugin-hook') + + '- for more info, visit https://docs.nodebb.org/en/latest/plugins/create.html#getting-a-reference-to-each-request-from-within-any-plugin-hook'); delete Plugins.deprecatedHooksParams[_hook]; } } From 8920c952817a0c53c0e86cee631a1b27008f3748 Mon Sep 17 00:00:00 2001 From: Aziz Khoury Date: Fri, 15 Apr 2016 17:07:24 -0400 Subject: [PATCH 13/15] reqFromSocket now support payload and event and uid --- src/socket.io/index.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/socket.io/index.js b/src/socket.io/index.js index e417f6d250..5706c0b62c 100644 --- a/src/socket.io/index.js +++ b/src/socket.io/index.js @@ -209,12 +209,15 @@ var cls = require('../middleware/cls'); }; - Sockets.reqFromSocket = function (socket) { + Sockets.reqFromSocket = function (socket, payload, event) { var headers = socket.request.headers; var host = headers.host; var referer = headers.referer || ''; + var data = ((payload || {}).data || []); return { + uid: socket.uid, + body: {event: event || data[0], params: data[1], payload: payload}, ip: headers['x-forwarded-for'] || socket.ip, host: host, protocol: socket.request.connection.encrypted ? 'https' : 'http', From aac30cb5ec5aedc7f8d0edad82f9b8172cd6757c Mon Sep 17 00:00:00 2001 From: Aziz Khoury Date: Fri, 15 Apr 2016 17:17:54 -0400 Subject: [PATCH 14/15] hmm .. --- src/socket.io/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/socket.io/index.js b/src/socket.io/index.js index 5706c0b62c..42647fb9a9 100644 --- a/src/socket.io/index.js +++ b/src/socket.io/index.js @@ -217,7 +217,9 @@ var cls = require('../middleware/cls'); return { uid: socket.uid, - body: {event: event || data[0], params: data[1], payload: payload}, + params: data[1], + method: event, + body: payload, ip: headers['x-forwarded-for'] || socket.ip, host: host, protocol: socket.request.connection.encrypted ? 'https' : 'http', From 3dc63438debb9cf37e6cb22c1711cb1f1c23b2e2 Mon Sep 17 00:00:00 2001 From: Aziz Khoury Date: Fri, 15 Apr 2016 17:21:26 -0400 Subject: [PATCH 15/15] hmm-2 ... --- src/socket.io/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/socket.io/index.js b/src/socket.io/index.js index 42647fb9a9..fc97f7cc87 100644 --- a/src/socket.io/index.js +++ b/src/socket.io/index.js @@ -218,7 +218,7 @@ var cls = require('../middleware/cls'); return { uid: socket.uid, params: data[1], - method: event, + method: event || data[0], body: payload, ip: headers['x-forwarded-for'] || socket.ip, host: host,