-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add context propagation middleware #337
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
var loopback = require('../../'); | ||
var app = loopback(); | ||
|
||
// Create a LoopBack context for all requests | ||
app.use(loopback.context()); | ||
|
||
// Store a request property in the context | ||
app.use(function saveHostToContext(req, res, next) { | ||
var ns = loopback.getCurrentContext(); | ||
ns.set('host', req.host); | ||
next(); | ||
}); | ||
|
||
app.use(loopback.rest()); | ||
|
||
var Color = loopback.createModel('color', { 'name': String }); | ||
Color.beforeRemote('**', function (ctx, unused, next) { | ||
// Inside LoopBack code, you can read the property from the context | ||
var ns = loopback.getCurrentContext(); | ||
console.log('Request to host', ns && ns.get('host')); | ||
next(); | ||
}); | ||
|
||
app.dataSource('db', { connector: 'memory' }); | ||
app.model(Color, { dataSource: 'db' }); | ||
|
||
app.listen(3000, function() { | ||
console.log('A list of colors is available at http://localhost:3000/colors'); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
var loopback = require('../loopback'); | ||
var juggler = require('loopback-datasource-juggler'); | ||
var remoting = require('strong-remoting'); | ||
var cls = require('continuation-local-storage'); | ||
|
||
module.exports = context; | ||
|
||
var name = 'loopback'; | ||
|
||
function createContext(scope) { | ||
// Make the namespace globally visible via the process.context property | ||
process.context = process.context || {}; | ||
var ns = process.context[scope]; | ||
if (!ns) { | ||
ns = cls.createNamespace(scope); | ||
process.context[scope] = ns; | ||
// Set up loopback.getCurrentContext() | ||
loopback.getCurrentContext = function() { | ||
return ns; | ||
}; | ||
|
||
chain(juggler); | ||
chain(remoting); | ||
} | ||
return ns; | ||
} | ||
|
||
function context(options) { | ||
options = options || {}; | ||
var scope = options.name || name; | ||
var enableHttpContext = options.enableHttpContext || false; | ||
var ns = createContext(scope); | ||
// Return the middleware | ||
return function contextHandler(req, res, next) { | ||
if (req.loopbackContext) { | ||
return next(); | ||
} | ||
req.loopbackContext = ns; | ||
// Bind req/res event emitters to the given namespace | ||
ns.bindEmitter(req); | ||
ns.bindEmitter(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(); | ||
}); | ||
}; | ||
} | ||
|
||
/** | ||
* Create a chained context | ||
* @param {Object} child The child context | ||
* @param {Object} parent The parent context | ||
* @private | ||
* @constructor | ||
*/ | ||
function ChainedContext(child, parent) { | ||
this.child = child; | ||
this.parent = parent; | ||
} | ||
|
||
/** | ||
* Get the value by name from the context. If it doesn't exist in the child | ||
* context, try the parent one | ||
* @param {String} name Name of the context property | ||
* @returns {*} Value of the context property | ||
*/ | ||
ChainedContext.prototype.get = function(name) { | ||
var val = this.child && this.child.get(name); | ||
if (val === undefined) { | ||
return this.parent && this.parent.get(name); | ||
} | ||
}; | ||
|
||
ChainedContext.prototype.set = function(name, val) { | ||
if (this.child) { | ||
return this.child.set(name, val); | ||
} else { | ||
return this.parent && this.parent.set(name, val); | ||
} | ||
}; | ||
|
||
ChainedContext.prototype.reset = function(name, val) { | ||
if (this.child) { | ||
return this.child.reset(name, val); | ||
} else { | ||
return this.parent && this.parent.reset(name, val); | ||
} | ||
}; | ||
|
||
function chain(child) { | ||
if (typeof child.getCurrentContext === 'function') { | ||
var childContext = new ChainedContext(child.getCurrentContext(), | ||
loopback.getCurrentContext()); | ||
child.getCurrentContext = function() { | ||
return childContext; | ||
}; | ||
} else { | ||
child.getCurrentContext = loopback.getCurrentContext; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I said before, I am opposed to exposing the http request & response objects to shared methods. Instead, there should be an easy and well-documented way how to write an express middleware that takes the relevant information from the request and set it on the context.
The new example code should look like this:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a different view here. Sure, the context should allow set/get specific context attributes. But I think there is nothing wrong by exposing req/res in the context considering a lot of express middlewares use req object as the context holder. I'm fine to introduce an option for the middleware to disable req/res.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are planning to support websocket based transport soon. What kind of req/resp objects is the websocket adapter going to provide in the context?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are going to be transport specific context for each of the protocols we support. Maybe in theory, the rest middleware should set up req/res instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The idea is that you should write methods that are unaware of the transport. This is really great for testability since you don't need to setup the transport to test the method... you just call it. It also means your code will work from node and over your desired transport. The goal is a clean separation between transport code and application code as well as being able to serialize the spec required to call the method over the transport.
The context object really helps shorten the list of arguments you have to pass to a method. It also removes the need to create a mapping to an argument (eg. the current user) that would be the same for every method.
Our goal should be that middleware (which ARE transport specific) parse out useful info (that is not transport specific) and provide that in the context.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ritch you have nailed it! 👍 💯