Fix context middleware to preserve domains
When executing a request using a pooled connection, connectors like MongoDB and/or MySQL rebind callbacks to the domain which issued the request, as opposed to the domain which opened the pooled connection. This commit fixes the context middleware to play nicely with that mechanism and preserve domain rebinds.
This commit is contained in:
parent
d77c5fac1d
commit
ca0208ddd9
|
@ -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();
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue