-
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
Conversation
I don't know enough about What is the rationale for adding this feature? I am not very happy about introducing another static/global dependency and coupling the remoted methods with the details of the REST transport. Consider the function from unit-tests:
How is this function supposed to be called standalone, when there is no context available? How is it supposed to work with different transports (e.g. json-rpc or websocket) that may not have |
var ns = cls.createNamespace(name); | ||
|
||
// Make the namespace globally visible via the process.context property | ||
process.context = ns; |
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.
maybe we should use the loopback.getCurrentContext()
(or similar) as public API and actually store on process
or globals
. This way we have the option to move around the actual location, and maintain an API.
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 somewhat need to have a global (variable/module) or access it via an existing one such as process. loopback.getCurrentContext() seems to be fine (internally we can set it to process.context.loopback too). It indicates that the context is created by loopback and only available via loopback.
I wonder how we pass the context to strong-remoting, loopback-datasource-juggler, or connectors though. There are a few use cases, such as:
- Expose a REST API via /api/users/me (me is the current logged in user id)
- Add a find method to a model which references the current user id
- Use the current security context to talk to facebook or other backend systems
Maybe each module should reference the context via .getCurrentContext() and loopback can inject its implementation onto them.
Is this really parallel safe ? |
I think the requirement is that you can provide mock contexts for tests. In general the context should not be used inside a remote method to get at transport specific information. Instead you should use it to get the current user and other application specific contextual data. If we provide a way to build the context for your application, you could provide one implementation for a test and others for non-tests. Something like the following: // in my tests
app.setContext = function(context, done) {
// includes req, res, accessToken, connection, ip, etc by default
app.models.User.create({
username: 'mytestuser',
password: '**********'
}, function(err, user) {
// ...
context.set('user', user);
done();
});
}
// my app's real implementation
app.setContext = function(context, done) {
// includes req, res, accessToken, connection, ip, etc by default
var userId = context.get('userId'); // available by default
if(userId) {
app.models.User.findById(userId, function(err, user) {
context.set('user', user);
});
} else {
process.nextTick(done);
}
} |
Yes. |
Any more comments? |
I'm not super excited about using continuation-local-storage. There was really no way to just pass a context object down the call chain? |
No, not all APIs allow you to pass in the context, especially across multiple modules on the call stack. Zone can serve the same purpose but it's not available for node 0.10. |
@piscisaureus do you know off the top, what 0.11 feature we are using which is not in 0.10? Is it possible to replace or mock out that functionality and then use Zones in loopback? |
Zones don't use any features that are new in v 0.12. (This happened because initially I wanted to use async-listeners for the implementation, but that didn't work out.) |
@raymondfeng Perhaps fixing zones to work with 0.10 is a better option in this case... |
What's the timeline? I'm very happy to use Zone for both 0.10 and 0.12. |
@raymondfeng looking into whats not working so I can give you and chanda an estimate |
// Run the code in the context of the namespace | ||
ns.set('req', req); | ||
ns.set('res', res); | ||
next(); |
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:
CartItem.sum = function(cartId, callback) {
// ...
var ns = loopback.getCurrentContext();
console.log(ns.get('requestUrl'));
};
// elsewhere - add a middleware to fill the context
app.use(function(req, res, next) {
var ns = loopback.getCurrentContext();
ns.set('requestUrl', req.url);
next();
});
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.
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.
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.
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.
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! 👍 💯
@raymondfeng What is your timeline on this? Clearly we shouldn't re-invent. Can you wait till after July 22nd for @kraman to dive into this? He thinks its doable, but won't know until he investigates. |
Chatted with @raymondfeng Without stopping this work, we'll find time to get Zones working with 0.10, and you can switch once its released. |
Continuation-local-storage doesn't work with current implementation of loopback-connector-mongodb. |
The use of domain instead of cls will fix this bug of broken context when a callback is called through an eventEmmiter. |
@lchenay I tested something similar to this https://github.com/brianc/node-domain-middleware, together with loopback/mongodb, and I can confirm this to be working. I'm using this with this my own branch: https://github.com/fabien/loopback-connector-mongodb/tree/scoping which allows dynamic scoping during runtime, for multi-tenancy for example. This can probably be generalized into loopback-datasource-juggler, but for now I chose to solve this at the connector-level in order to cover all the db interactions. |
@raymondfeng ping |
ping |
@@ -5,6 +5,7 @@ var memory = loopback.createDataSource({ | |||
connector: loopback.Memory | |||
}); | |||
|
|||
server.use(loopback.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.
IMHO the context propagation should be added automatically by loopback()
as the first middleware in the chain.
That way the minimal loopback app still keeps working:
var app = loopback();
app.use(loopback.rest());
app.listen();
What are the situations when a developer will not want to use loopback.context()
? My understanding is that features like transactions will require the context , thus it is required to include the context middleware anyways.
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.
@raymondfeng comment to address ^^
I am proposing the following steps to move this forward:
|
6b0a0a5
to
3d4db3b
Compare
See also #708
Let's overwrite it. It seems to me that merge will be always confusing - is |
f112c1e
to
42c3475
Compare
@raymondfeng I have reworked the integration with
Please review the commits I have added, possibly also the whole patch and let me know if it is good get merged. If you agree, then I'll squash your commits to clean up the history and land the PR. |
42c3475
to
d771e41
Compare
I'm back on the bug with cls and mongodb adapter. mongodb adapter, break domain naturally. It contain a workaround to rebind domain on callback when using function that break it. cf: https://github.com/mongodb/node-mongodb-native/blob/1.4/lib/mongodb/db.js#L1637 Cls do not use domain, and so do not use process.domain. So using cls is not fixed by this workaround. I found a quick fix that save us. I'm sure it's not the best solution, but currently it's working. I'm creating a domain around cls, and when a bind is call on this domain, i also bind the cls namespace. Other library use same fix as mongodb and rely on process.domain: |
- Implement the middleware `loopback.context` - Inject context into juggler and strong-remoting - Make http context optional and default to false - Optionally mount context middleware from `loopback.rest`
Modify `loopback.rest()` to read the configuration for `loopback.context` from `app.get('remoting')`, which is the approach used for all other configuration options related to the REST transport.
d771e41
to
4fdcbd1
Compare
Add context propagation middleware
Landed. @lchenay could you please send a pull request with the patch you are proposing? |
2.8.0 * Expose more loopback middleware for require (Raymond Feng) * Scope app middleware to a list of paths (Miroslav Bajtoš) * Update CONTRIBUTING.md (Alex Voitau) * Fix the model name for hasMany/through relation (Raymond Feng) * Fixing the model attach (wfgomes) * Minor: update jsdoc for PersistedModel.updateAll (Alex Voitau) * AccessToken: optional `options` in findForRequest (Miroslav Bajtoš) * server-app: improve jsdoc comments (Miroslav Bajtoš) * server-app: middleware API improvements (Miroslav Bajtoš) * typo of port server (wfgomes) * Move middleware sources to `server/middleware` (Miroslav Bajtoš) * app.middleware: verify serial exec of handlers (Miroslav Bajtoš) * Simplify `app.defineMiddlewarePhases` (Miroslav Bajtoš) * Make sure loopback has all properties from express (Raymond Feng) * Implement `app.defineMiddlewarePhases` (Miroslav Bajtoš) * Implement app.middlewareFromConfig (Miroslav Bajtoš) * middleware/token: store the token in current ctx (Miroslav Bajtoš) * Fix `loopback.getCurrentContext` (Miroslav Bajtoš) * Update chai to ^1.10.0 (Miroslav Bajtoš) * package: fix deps (Miroslav Bajtoš) * Middleware phases - initial implementation (Miroslav Bajtoš) * Allows ACLs/settings in model config (Raymond Feng) * Remove context middleware per Ritchie (Rand McKinney) * Add API doc for context middleware - see #337 (crandmck) * Update persisted-model.js (Rand McKinney) * rest middleware: clean up context config (Miroslav Bajtoš) * Move `context` example to a standalone app (Miroslav Bajtoš) * Enable the context middleware from loopback.rest (Raymond Feng) * Add context propagation middleware (Raymond Feng) * Changes to JSdoc comments (Rand McKinney) * Reorder classes alphabetically in each section (Rand McKinney) * common: coding style cleanup (Miroslav Bajtoš) * Coding style cleanup (Gruntfile, lib) (Miroslav Bajtoš) * Enable jscs for `lib`, fix style violations (Rob Halff) * Add access-context.js to API doc (Rand McKinney) * Remove doc for debug function (Rand McKinney) * Update registry.js (Rand McKinney) * Fix the jsdoc for User.login (Raymond Feng) * Deleted instantiation of new Change model. This PR removes the instantiation of a new change model as models return from Change.find are already instances of Change. This solves the duplicate Id issue #649 (Berkeley Martinez) * Expose path to the built-in favicon file (Miroslav Bajtoš) * Add API docs for `loopback.static`. (Miroslav Bajtoš) * Add test for `remoting.rest.supportedTypes` (Miroslav Bajtoš) * Revert "rest handler options" (Miroslav Bajtoš) * REST handler options. (Guilherme Cirne) * The elapsed time in milliseconds can be 0 (less than 1 ms) (Raymond Feng)
/to @ritch @bajtos @piscisaureus