diff --git a/server/middleware/context.js b/server/middleware/context.js index c86903a0..23912db4 100644 --- a/server/middleware/context.js +++ b/server/middleware/context.js @@ -2,6 +2,7 @@ var loopback = require('../../lib/loopback'); var juggler = require('loopback-datasource-juggler'); var remoting = require('strong-remoting'); var cls = require('continuation-local-storage'); +var domain = require('domain'); module.exports = context; @@ -44,6 +45,13 @@ function context(options) { var scope = options.name || name; var enableHttpContext = options.enableHttpContext || false; var ns = createContext(scope); + + var currentDomain = process.domain = domain.create(); + currentDomain.oldBind = currentDomain.bind; + currentDomain.bind = function(callback, context) { + return currentDomain.oldBind(ns.bind(callback, context), context); + }; + // Return the middleware return function contextHandler(req, res, next) { if (req.loopbackContext) { @@ -53,13 +61,19 @@ function context(options) { // Bind req/res event emitters to the given namespace ns.bindEmitter(req); ns.bindEmitter(res); + + currentDomain.add(req); + currentDomain.add(res); + // Create namespace for the request context - ns.run(function processRequestInContext(context) { - // Run the code in the context of the namespace - if (enableHttpContext) { - ns.set('http', {req: req, res: res}); // Set up the transport context - } - next(); + currentDomain.run(function() { + ns.run(function processRequestInContext(context) { + // Run the code in the context of the namespace + if (enableHttpContext) { + ns.set('http', {req: req, res: res}); // Set up the transport context + } + next(); + }); }); }; } diff --git a/test/loopback.test.js b/test/loopback.test.js index 8f217f6a..26c4f22f 100644 --- a/test/loopback.test.js +++ b/test/loopback.test.js @@ -1,4 +1,7 @@ var it = require('./util/it'); +var describe = require('./util/describe'); +var Domain = require('domain'); +var EventEmitter = require('events').EventEmitter; describe('loopback', function() { var nameCounter = 0; @@ -388,4 +391,72 @@ describe('loopback', function() { }); }); }); + + describe.onServer('loopback.getCurrentContext', function() { + var runInOtherDomain; + var runnerInterval; + + before(function setupRunInOtherDomain() { + var emitterInOtherDomain = new EventEmitter(); + Domain.create().add(emitterInOtherDomain); + + runInOtherDomain = function(fn) { + emitterInOtherDomain.once('run', fn); + }; + + runnerInterval = setInterval(function() { + emitterInOtherDomain.emit('run'); + }, 10); + }); + + after(function tearDownRunInOtherDomain() { + clearInterval(runnerInterval); + }); + + // See the following two items for more details: + // https://github.com/strongloop/loopback/issues/809 + // https://github.com/strongloop/loopback/pull/337#issuecomment-61680577 + it('preserves callback domain', function(done) { + var app = loopback(); + app.use(loopback.rest()); + app.dataSource('db', { connector: 'memory' }); + + var TestModel = loopback.createModel({ name: 'TestModel' }); + app.model(TestModel, { dataSource: 'db', public: true }); + + // function for remote method + TestModel.test = function(inst, cb) { + var tmpCtx = loopback.getCurrentContext(); + if (tmpCtx) tmpCtx.set('data', 'a value stored in context'); + if (process.domain) cb = process.domain.bind(cb); // IMPORTANT + runInOtherDomain(cb); + }; + + // remote method + TestModel.remoteMethod('test', { + accepts: { arg: 'inst', type: uniqueModelName }, + returns: { root: true }, + http: { path: '/test', verb: 'get' } + }); + + // after remote hook + TestModel.afterRemote('**', function(ctxx, inst, next) { + var tmpCtx = loopback.getCurrentContext(); + if (tmpCtx) { + ctxx.result.data = tmpCtx.get('data'); + }else { + ctxx.result.data = 'context not available'; + } + next(); + }); + + request(app) + .get('/TestModels/test') + .end(function(err, res) { + if (err) return done(err); + expect(res.body.data).to.equal('a value stored in context'); + done(); + }); + }); + }); });