Compare commits
58 Commits
Author | SHA1 | Date |
---|---|---|
|
6cb6fc879b | |
|
74016f5f90 | |
|
3ead6521e0 | |
|
e7584e23d7 | |
|
5c4376a54e | |
|
23b14733a6 | |
|
1dd8282826 | |
|
824ebec5e0 | |
|
06f0d16701 | |
|
a575d934a7 | |
|
5490cbfe18 | |
|
3376b1ef74 | |
|
9631185843 | |
|
91fa368457 | |
|
d4622392d3 | |
|
d54b970161 | |
|
391d9eb2ad | |
|
a41a55b3aa | |
|
eef46ef9bb | |
|
5931f5351f | |
|
f561dc5296 | |
|
8ddd939c54 | |
|
22d95df742 | |
|
c15c89d735 | |
|
c5b303fe5b | |
|
ea61dfdb3a | |
|
fcaa7adc61 | |
|
d7b74e293e | |
|
bcfbc2a163 | |
|
eafbc7f6fd | |
|
63a4c80678 | |
|
66c1954441 | |
|
b9fa114ad6 | |
|
3c10d219c7 | |
|
4d4920c491 | |
|
6d5fedb85e | |
|
f49752780b | |
|
ebdbb7bfe7 | |
|
3a3b2fd676 | |
|
2f236b884e | |
|
d5f977fc4a | |
|
06b99c26df | |
|
750d8bb5c9 | |
|
b0e0b350d4 | |
|
bfed31f2dc | |
|
db9387e56b | |
|
996a49f7da | |
|
7cef4817c4 | |
|
0bc1ac0886 | |
|
15eb91df7d | |
|
30218be9e5 | |
|
3aeee7dbe4 | |
|
89a1e4ae23 | |
|
1844bfcd22 | |
|
814171d2b3 | |
|
46cfc17600 | |
|
268716f69e | |
|
7d0f6afaef |
|
@ -0,0 +1,50 @@
|
||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
labels: bug
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- 🚨 STOP 🚨 STOP 🚨 STOP 🚨
|
||||||
|
|
||||||
|
HELP US HELP YOU, PLEASE
|
||||||
|
- Do a quick search to avoid duplicate issues
|
||||||
|
- Provide as much information as possible (reproduction sandbox, use case for features, etc.)
|
||||||
|
- Consider using a more suitable venue for questions such as Stack Overflow, Gitter, etc.
|
||||||
|
|
||||||
|
Please fill in the *entire* template below.
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Steps to reproduce
|
||||||
|
|
||||||
|
<!-- Describe how to reproduce the issue -->
|
||||||
|
|
||||||
|
## Current Behavior
|
||||||
|
|
||||||
|
<!-- Describe the observed result -->
|
||||||
|
|
||||||
|
## Expected Behavior
|
||||||
|
|
||||||
|
<!-- Describe what did you expect instead, what is the desired outcome? -->
|
||||||
|
|
||||||
|
## Link to reproduction sandbox
|
||||||
|
|
||||||
|
<!--
|
||||||
|
See https://loopback.io/doc/en/contrib/Reporting-issues.html#loopback-3x-bugs
|
||||||
|
Note: Failure to provide a sandbox application for reproduction purposes will result in the issue being closed.
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Additional information
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Copy+paste the output of these two commands:
|
||||||
|
node -e 'console.log(process.platform, process.arch, process.versions.node)'
|
||||||
|
npm ls --prod --depth 0 | grep loopback
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Related Issues
|
||||||
|
|
||||||
|
<!-- Did you find other bugs that looked similar? -->
|
||||||
|
|
||||||
|
_See [Reporting Issues](http://loopback.io/doc/en/contrib/Reporting-issues.html) for more tips on writing good issues_
|
|
@ -0,0 +1,25 @@
|
||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
labels: feature
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Suggestion
|
||||||
|
|
||||||
|
<!-- A summary of what you'd like to see added or changed -->
|
||||||
|
|
||||||
|
## Use Cases
|
||||||
|
|
||||||
|
<!--
|
||||||
|
What do you want to use this for?
|
||||||
|
What shortcomings exist with current approaches?
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
<!-- Show how this would be used and what the behavior would be -->
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
TBD - will be filled by the team.
|
|
@ -0,0 +1,27 @@
|
||||||
|
---
|
||||||
|
name: Question
|
||||||
|
about: The issue tracker is not for questions. Please use Stack Overflow or other resources for help.
|
||||||
|
labels: question
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- 🚨 STOP 🚨 STOP 🚨 STOP 🚨
|
||||||
|
|
||||||
|
THE ISSUE TRACKER IS NOT FOR QUESTIONS.
|
||||||
|
|
||||||
|
DO NOT CREATE A NEW ISSUE TO ASK A QUESTION.
|
||||||
|
|
||||||
|
Please use one of the following resources for help:
|
||||||
|
|
||||||
|
**Questions**
|
||||||
|
|
||||||
|
- https://stackoverflow.com/tags/loopbackjs
|
||||||
|
- https://groups.google.com/forum/#!forum/loopbackjs
|
||||||
|
- https://gitter.im/strongloop/loopback
|
||||||
|
|
||||||
|
**Immediate support**
|
||||||
|
|
||||||
|
- https://strongloop.com/api-connect-faqs/
|
||||||
|
- https://strongloop.com/node-js/subscription-plans/
|
||||||
|
|
||||||
|
-->
|
|
@ -0,0 +1,11 @@
|
||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: Report a security vulnerability
|
||||||
|
url: https://loopback.io/doc/en/contrib/Reporting-issues.html#security-issues
|
||||||
|
about: Do not report security vulnerabilities using GitHub issues. Please send an email to `reachsl@us.ibm.com` instead.
|
||||||
|
- name: Get help on StackOverflow
|
||||||
|
url: https://stackoverflow.com/tags/loopbackjs
|
||||||
|
about: Please ask and answer questions on StackOverflow.
|
||||||
|
- name: Join our mailing list
|
||||||
|
url: https://groups.google.com/forum/#!forum/loopbackjs
|
||||||
|
about: You can also post your question to our mailing list.
|
|
@ -0,0 +1,18 @@
|
||||||
|
<!--
|
||||||
|
Please provide a high-level description of the changes made by your pull request.
|
||||||
|
|
||||||
|
Include references to all related GitHub issues and other pull requests, for example:
|
||||||
|
|
||||||
|
Fixes #123
|
||||||
|
Implements #254
|
||||||
|
See also #23
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Checklist
|
||||||
|
|
||||||
|
👉 [Read and sign the CLA (Contributor License Agreement)](https://cla.strongloop.com/agreements/strongloop/loopback-context) 👈
|
||||||
|
|
||||||
|
- [ ] `npm test` passes on your machine
|
||||||
|
- [ ] New tests added or existing tests modified to cover all changes
|
||||||
|
- [ ] Code conforms with the [style guide](https://loopback.io/doc/en/contrib/style-guide-es6.html)
|
||||||
|
- [ ] Commit messages are following our [guidelines](https://loopback.io/doc/en/contrib/git-commit-messages.html)
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Number of days of inactivity before an issue becomes stale
|
||||||
|
daysUntilStale: 60
|
||||||
|
# Number of days of inactivity before a stale issue is closed
|
||||||
|
daysUntilClose: 14
|
||||||
|
# Issues with these labels will never be considered stale
|
||||||
|
exemptLabels:
|
||||||
|
- pinned
|
||||||
|
- security
|
||||||
|
- critical
|
||||||
|
- p1
|
||||||
|
- major
|
||||||
|
- good first issue
|
||||||
|
# Label to use when marking an issue as stale
|
||||||
|
staleLabel: stale
|
||||||
|
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||||
|
markComment: >
|
||||||
|
This issue has been automatically marked as stale because it has not had
|
||||||
|
recent activity. It will be closed if no further activity occurs. Thank you
|
||||||
|
for your contributions.
|
||||||
|
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||||
|
closeComment: >
|
||||||
|
This issue has been closed due to continued inactivity. Thank you for your understanding.
|
||||||
|
If you believe this to be in error, please contact one of the code owners,
|
||||||
|
listed in the `CODEOWNERS` file at the top-level of this repository.
|
|
@ -1,7 +1,6 @@
|
||||||
sudo: false
|
sudo: false
|
||||||
language: node_js
|
language: node_js
|
||||||
node_js:
|
node_js:
|
||||||
- "0.10"
|
- "8"
|
||||||
- "0.12"
|
- "10"
|
||||||
- "4"
|
- "12"
|
||||||
- "6"
|
|
||||||
|
|
86
CHANGES.md
86
CHANGES.md
|
@ -1,3 +1,89 @@
|
||||||
|
2021-01-19, Version 3.5.2
|
||||||
|
=========================
|
||||||
|
|
||||||
|
* chore: update LTS status to End-of-Life (Rifa Achrinza)
|
||||||
|
|
||||||
|
|
||||||
|
2020-03-06, Version 3.5.1
|
||||||
|
=========================
|
||||||
|
|
||||||
|
* Update LTS status in README (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
|
||||||
|
2020-02-17, Version 3.5.0
|
||||||
|
=========================
|
||||||
|
|
||||||
|
* Add support for Node.js 12 (Ahmet Cetin)
|
||||||
|
|
||||||
|
* chore: enable stalebot (Diana Lau)
|
||||||
|
|
||||||
|
* chore: improve issue and PR templates (Nora)
|
||||||
|
|
||||||
|
* Drop support for Node.js 6.x (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* chore: update copyrights years (Agnes Lin)
|
||||||
|
|
||||||
|
|
||||||
|
2019-01-02, Version 3.4.0
|
||||||
|
=========================
|
||||||
|
|
||||||
|
* Travis: enable Node.js 10.x (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Node version ^10.14 added to engines list (Ahmet Cetin)
|
||||||
|
|
||||||
|
* add lts annoucement (jannyHou)
|
||||||
|
|
||||||
|
* Upgrade dependecies, fix new linter errors (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Drop support for Node.js 4.x, 5.x and 7.x (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Create Issue and PR Templates (#36) (Sakib Hasan)
|
||||||
|
|
||||||
|
* Add CODEOWNERS file (Diana Lau)
|
||||||
|
|
||||||
|
|
||||||
|
2017-07-21, Version 3.3.0
|
||||||
|
=========================
|
||||||
|
|
||||||
|
* add nodejs v8.2.1 support (Oleg Kubrakov)
|
||||||
|
|
||||||
|
|
||||||
|
2017-07-10, Version 3.2.0
|
||||||
|
=========================
|
||||||
|
|
||||||
|
* Add Node.js 7 to package.json and .travis.yml (Edgars Zagorskis)
|
||||||
|
|
||||||
|
|
||||||
|
2017-03-17, Version 3.1.0
|
||||||
|
=========================
|
||||||
|
|
||||||
|
* fix typo in readme (biniam)
|
||||||
|
|
||||||
|
* Add bind option to getCurrentContext() (Emiliano Daddario)
|
||||||
|
|
||||||
|
* Upgrade eslint & config to latest (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
|
||||||
|
2017-01-06, Version 3.0.0
|
||||||
|
=========================
|
||||||
|
|
||||||
|
* Rework README, add info from docs site (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* Drop continuation-local-storage, use cls-hooked (josieusa)
|
||||||
|
|
||||||
|
* Update paid support URL (Siddhi Pai)
|
||||||
|
|
||||||
|
* Add app using loopback-context (Amir Jafarian)
|
||||||
|
|
||||||
|
* Drop support for Node v0.10 and v0.12 (Siddhi Pai)
|
||||||
|
|
||||||
|
* Start the development of the next major version (Siddhi Pai)
|
||||||
|
|
||||||
|
* Update deps to loopback 3.0.0 RC (Miroslav Bajtoš)
|
||||||
|
|
||||||
|
* make warning less ambigious (Carl Fürstenberg)
|
||||||
|
|
||||||
|
|
||||||
2016-08-10, Version 1.0.0
|
2016-08-10, Version 1.0.0
|
||||||
=========================
|
=========================
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
# Lines starting with '#' are comments.
|
||||||
|
# Each line is a file pattern followed by one or more owners,
|
||||||
|
# the last matching pattern has the most precendence.
|
||||||
|
|
||||||
|
# Core team members from IBM
|
||||||
|
* @bajtos @josieusa
|
|
@ -1,9 +1,9 @@
|
||||||
### Contributing ###
|
### Contributing ###
|
||||||
|
|
||||||
Thank you for your interest in `loopback-context-cls`, an open source project
|
Thank you for your interest in `loopback-context`, an open source project
|
||||||
administered by StrongLoop.
|
administered by StrongLoop.
|
||||||
|
|
||||||
Contributing to `loopback-context-cls` is easy. In a few simple steps:
|
Contributing to `loopback-context` is easy. In a few simple steps:
|
||||||
|
|
||||||
* Ensure that your effort is aligned with the project's roadmap by
|
* Ensure that your effort is aligned with the project's roadmap by
|
||||||
talking to the maintainers, especially if you are going to spend a
|
talking to the maintainers, especially if you are going to spend a
|
||||||
|
|
238
README.md
238
README.md
|
@ -1,25 +1,90 @@
|
||||||
# loopback-context
|
# loopback-context
|
||||||
|
|
||||||
Current context for LoopBack applications, based on
|
**⚠️ LoopBack 3 has reached end of life. We are no longer accepting pull requests or providing
|
||||||
node-continuation-local-storage.
|
support for community users. The only exception is fixes for critical bugs and security
|
||||||
|
vulnerabilities provided as part of support for IBM API Connect customers. (See
|
||||||
|
[Module Long Term Support Policy](#module-long-term-support-policy) below.)**
|
||||||
|
|
||||||
|
We urge all LoopBack 3 users to migrate their applications to LoopBack 4 as
|
||||||
|
soon as possible. Refer to our
|
||||||
|
[Migration Guide](https://loopback.io/doc/en/lb4/migration-overview.html)
|
||||||
|
for more information on how to upgrade.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Current context for LoopBack applications, based on cls-hooked.
|
||||||
|
|
||||||
## WARNING
|
## WARNING
|
||||||
|
|
||||||
The module node-continuation-local-storage is known to have many problems,
|
**`cls-hooked` module uses undocumented `AsyncWrap` API that was introduced to Node.js relatively recently. While this new API seems to be more reliable than the old `async-listener` used by `continuation-local-storage`, there are still cases where the context (local storage) is not preserved correctly. Please consider this risk before using loopback-context.**
|
||||||
see e.g. [issue #59](https://github.com/othiym23/node-continuation-local-storage/issues/59).
|
|
||||||
As a result, loopback-context does not work in many situations, as can be
|
|
||||||
seen from issues reported in LoopBack's
|
|
||||||
[issue tracker](https://github.com/strongloop/loopback/issues?utf8=%E2%9C%93&q=is%3Aissue%20getCurrentcontext).
|
|
||||||
|
|
||||||
**We recommend AGAINST using this module.**
|
### Known issues
|
||||||
|
|
||||||
If you are running on Node v6, you can try the new alternative
|
- [when](https://www.npmjs.com/package/when), a popular Promise
|
||||||
[cls-hooked](https://github.com/Jeff-Lewis/cls-hooked).
|
implementation, breaks context propagation. Please consider using the
|
||||||
|
built-in `Promise` implementation provided by Node.js or
|
||||||
|
[Bluebird](https://www.npmjs.com/package/bluebird) instead.
|
||||||
|
- Express middleware chains which contain a "bad" middleware (i.e. one which
|
||||||
|
breaks context propagation inside its function body, in a way mentioned in
|
||||||
|
this doc) especially if called before other "good" ones needs refactoring,
|
||||||
|
in order to prevent the context from getting mixed up among HTTP requests.
|
||||||
|
See usage below for details.
|
||||||
|
|
||||||
|
Discussion: https://github.com/strongloop/loopback-context/issues/17
|
||||||
|
|
||||||
|
In general, any module that implements a custom task queue or a connection pool
|
||||||
|
is prone to break context storage. This is an inherent problem of continuation
|
||||||
|
local storage that needs to be fixed at lower level - first in Node.js core
|
||||||
|
and then in modules implementing task queues and connection pools.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```
|
||||||
|
$ npm install --save loopback-context cls-hooked
|
||||||
|
```
|
||||||
|
|
||||||
|
Make sure you are running on a Node.js version supported by this module
|
||||||
|
(`^4.5`, `^5.10`, `^6.0`, `^7.0`, `^8.2.1` or `^10.14`). When installing, check the output of `npm install`
|
||||||
|
and make sure there are no `engine` related warnings.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
1) Add `per-request` middleware to your
|
### Setup cls-hooked
|
||||||
`server/middleware-config.json`:
|
|
||||||
|
To minimize the likelihood of loosing context in your application, you should
|
||||||
|
ensure that `cls-hooked` is loaded as the first module of your application, so
|
||||||
|
that it can wrap certain Node.js APIs before any other modules start using these
|
||||||
|
APIs.
|
||||||
|
|
||||||
|
Our recommended approach is to add `-r cls-hooked` to node's list of
|
||||||
|
arguments when starting your LoopBack application.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ node -r cls-hooked .
|
||||||
|
```
|
||||||
|
|
||||||
|
If you are using a process manager like `strong-pm` or `pm2`, then consult
|
||||||
|
their documentation whether it's possible to configure the arguments used to
|
||||||
|
spawn worker processes. Note that `slc run` does not support this feature yet,
|
||||||
|
see [strong-supervisor#56](https://github.com/strongloop/strong-supervisor/issues/56).
|
||||||
|
|
||||||
|
Alternatively, you can add the following line as the first line of your main
|
||||||
|
application file:
|
||||||
|
|
||||||
|
```js
|
||||||
|
require('cls-hooked');
|
||||||
|
```
|
||||||
|
|
||||||
|
This approach should be compatible with all process managers, including
|
||||||
|
`strong-pm`. However, we feel that relying on the order of `require` statements
|
||||||
|
is error-prone.
|
||||||
|
|
||||||
|
### Configure context propagation
|
||||||
|
|
||||||
|
To setup your LoopBack application to create a new context for each incoming
|
||||||
|
HTTP request, configure `per-context` middleware in your
|
||||||
|
`server/middleware.json` as follows:
|
||||||
|
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
@ -30,7 +95,28 @@ If you are running on Node v6, you can try the new alternative
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
2) Then you can access the context from your code:
|
**IMPORTANT: By default, the HTTP req/res objects are not set onto the current context. You
|
||||||
|
need to set `enableHttpContext` to true to enable automatic population
|
||||||
|
of req/res objects.**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"initial": {
|
||||||
|
"loopback-context#per-request": {
|
||||||
|
"params": {
|
||||||
|
"enableHttpContext": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Use the current context
|
||||||
|
|
||||||
|
Once you’ve enabled context propagation, you can access the current context
|
||||||
|
object using `LoopBackContext.getCurrentContext()`. The context will be
|
||||||
|
available in middleware (if it is loaded after the context middleware),
|
||||||
|
remoting hooks, model hooks, and custom methods.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
var LoopBackContext = require('loopback-context');
|
var LoopBackContext = require('loopback-context');
|
||||||
|
@ -44,6 +130,126 @@ MyModel.myMethod = function(cb) {
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
See the official LoopBack
|
### Bind for concurrency
|
||||||
[documentation](https://docs.strongloop.com/display/APIC/Using+current+context)
|
|
||||||
for more details.
|
In order to workaround the aforementioned concurrency issue with `when` (and
|
||||||
|
similar `Promise`-like and other libraries implementing custom queues and/or
|
||||||
|
connection pools), it's recommended to activate context binding inside each
|
||||||
|
HTTP request or concurrent `runInContext()` call, by using the `bind` option, as
|
||||||
|
in this example:
|
||||||
|
|
||||||
|
var ctx = LoopBackContext.getCurrentContext({ bind: true });
|
||||||
|
|
||||||
|
With the option enabled, this both creates the context, and binds the access
|
||||||
|
methods of the context (i.e. `get` and `set`), at once.
|
||||||
|
|
||||||
|
**Warning**: this only works if it's **the first expression evaluated** in every
|
||||||
|
middleware/operation hook/`runInContext()` call etc. that uses
|
||||||
|
`getCurrentContext`. (It must be the first expression; it may not be enough if
|
||||||
|
it's at the first line). Explanation: you must bind the context while it's still
|
||||||
|
correct, i.e. before it gets mixed up between concurrent operations affected by
|
||||||
|
bugs. Therefore, to be sure, you must bind it before *any* operation.
|
||||||
|
|
||||||
|
Also, with respect to the "bad", context-breaking middleware use case mentioned in "Known issues"
|
||||||
|
before, the following 2 lines need to be present at the beginning of the middleware
|
||||||
|
body. At least the "bad" one; but, as a preventive measure, they can be present
|
||||||
|
in every other middleware of every chain as well, being backward-compatible:
|
||||||
|
|
||||||
|
var badMiddleware = function(req, res, next) {
|
||||||
|
// these 2 lines below are needed
|
||||||
|
var ctx = LoopBackContext.getCurrentContext({bind: true});
|
||||||
|
next = ctx.bind(next);
|
||||||
|
...
|
||||||
|
|
||||||
|
The `bind` option defaults to `false`. This is only in order to prevent breaking
|
||||||
|
legacy apps; but if your app doesn't have such issue, then you can safely use
|
||||||
|
`bind: true` everywhere in your app (e.g. with a
|
||||||
|
[codemod](https://github.com/facebook/jscodeshift), or by monkey-patching
|
||||||
|
`getCurrentContext()` globally, if you prefer an automated fashion).
|
||||||
|
|
||||||
|
**Warning**: this only applies to application modules. In fact, if the module
|
||||||
|
affected by the concurrency issue is of this kind, you can easily refactor/write
|
||||||
|
your own code so to enable `bind`. Not if it's a 3rd-party module, nor a
|
||||||
|
Loopback non-core module, unless you fork and fix it.
|
||||||
|
|
||||||
|
### Use current authenticated user in remote methods
|
||||||
|
|
||||||
|
In advanced use cases, for example when you want to add custom middleware, you
|
||||||
|
have to add the context middleware at the right position in the middleware
|
||||||
|
chain (before the middleware that depends on
|
||||||
|
`LoopBackContext.getCurrentContext`).
|
||||||
|
|
||||||
|
**IMPORTANT: `LoopBackContext.perRequest()` detects the situation when it is
|
||||||
|
invoked multiple times on the same request and returns immediately in
|
||||||
|
subsequent runs.**
|
||||||
|
|
||||||
|
Here is a snippet using a middleware function to place the currently
|
||||||
|
authenticated user into the context so that remote methods may use it:
|
||||||
|
|
||||||
|
**server/middleware/store-current-user.js**
|
||||||
|
```js
|
||||||
|
module.exports = function(options) {
|
||||||
|
return function storeCurrentUser(req, res, next) {
|
||||||
|
if (!req.accessToken) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
app.models.UserModel.findById(req.accessToken.userId, function(err, user) {
|
||||||
|
if (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
if (!user) {
|
||||||
|
return next(new Error('No user with this access token was found.'));
|
||||||
|
}
|
||||||
|
var loopbackContext = LoopBackContext.getCurrentContext();
|
||||||
|
if (loopbackContext) {
|
||||||
|
loopbackContext.set('currentUser', user);
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
**server/middleware.json**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"initial": {
|
||||||
|
"loopback-context#per-request": {}
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"loopback#token": {}
|
||||||
|
},
|
||||||
|
"auth:after": {
|
||||||
|
"./middleware/store-current-user": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**common/models/YourModel.json**
|
||||||
|
```js
|
||||||
|
var LoopBackContext = require('loopback-context');
|
||||||
|
module.exports = function(YourModel) {
|
||||||
|
...
|
||||||
|
//remote method
|
||||||
|
YourModel.someRemoteMethod = function(arg1, arg2, cb) {
|
||||||
|
var ctx = LoopBackContext.getCurrentContext();
|
||||||
|
var currentUser = ctx && ctx.get('currentUser');
|
||||||
|
console.log('currentUser.username: ', currentUser.username); // voila!
|
||||||
|
...
|
||||||
|
cb(null);
|
||||||
|
};
|
||||||
|
...
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Module Long Term Support Policy
|
||||||
|
|
||||||
|
This module adopts the [Module Long Term Support (LTS)](http://github.com/CloudNativeJS/ModuleLTS) policy, with the following End Of Life (EOL) dates:
|
||||||
|
|
||||||
|
| Version | Status | Published | EOL |
|
||||||
|
| ------- | --------------- | --------- | -------- |
|
||||||
|
| 3.x | End-of-Life | Jan 2017 | Dec 2020 |
|
||||||
|
| 1.x | End-of-Life | Aug 2016 | Apr 2019 |
|
||||||
|
|
||||||
|
Learn more about our LTS plan in the [docs](https://loopback.io/doc/en/contrib/Long-term-support.html).
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// Copyright IBM Corp. 2015. All Rights Reserved.
|
// Copyright IBM Corp. 2016. All Rights Reserved.
|
||||||
// Node module: loopback-context-cls
|
// Node module: loopback-context
|
||||||
// This file is licensed under the MIT License.
|
// This file is licensed under the MIT License.
|
||||||
// License text available at https://opensource.org/licenses/MIT
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
// Copyright IBM Corp. 2016. All Rights Reserved.
|
||||||
|
// Node module: loopback-context
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var contextPerRequest = require('../server/middleware/per-request.js');
|
||||||
|
// Use `var lbContext = require('loopback-context');` in your app
|
||||||
|
var lbContext = require('../');
|
||||||
|
var loopback = require('loopback');
|
||||||
|
|
||||||
|
var app = loopback();
|
||||||
|
|
||||||
|
// Configure the context middleware
|
||||||
|
app.middleware('initial', contextPerRequest());
|
||||||
|
|
||||||
|
// Store a request property in the context
|
||||||
|
app.use(function saveHostToContext(req, res, next) {
|
||||||
|
var currentContext = lbContext.getCurrentContext();
|
||||||
|
if (currentContext)
|
||||||
|
currentContext.set('host', req.hostname);
|
||||||
|
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 currentContext = lbContext.getCurrentContext();
|
||||||
|
if (currentContext)
|
||||||
|
console.log('Request to host %s',
|
||||||
|
currentContext && currentContext.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');
|
||||||
|
});
|
28
package.json
28
package.json
|
@ -1,7 +1,10 @@
|
||||||
{
|
{
|
||||||
"name": "loopback-context",
|
"name": "loopback-context",
|
||||||
"version": "1.0.0",
|
"version": "3.5.2",
|
||||||
"description": "Current context for LoopBack applications, based on node-continuation-local-storage",
|
"description": "Current context for LoopBack applications, based on cls-hooked",
|
||||||
|
"engines": {
|
||||||
|
"node": "^8.2.1 || ^10.14 || ^12.15"
|
||||||
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"StrongLoop",
|
"StrongLoop",
|
||||||
"LoopBack",
|
"LoopBack",
|
||||||
|
@ -20,15 +23,18 @@
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"continuation-local-storage": "^3.1.7"
|
"cls-hooked": "^4.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"chai": "^3.5.0",
|
"async-1.5.2": "file:./test/stub-modules/async-1.5.2",
|
||||||
"dirty-chai": "^1.2.2",
|
"chai": "^4.1.2",
|
||||||
"eslint": "^2.13.1",
|
"dirty-chai": "^2.0.1",
|
||||||
"eslint-config-loopback": "^4.0.0",
|
"eslint": "^5.0.1",
|
||||||
"loopback": "^3.0.0-alpha.1",
|
"eslint-config-loopback": "^10.0.0",
|
||||||
"mocha": "^2.5.3",
|
"loopback": "^3.0.0",
|
||||||
"supertest": "^1.2.0"
|
"mocha": "^5.2.0",
|
||||||
}
|
"supertest": "^3.1.0",
|
||||||
|
"when-3.7.7": "file:./test/stub-modules/when-3.7.7"
|
||||||
|
},
|
||||||
|
"author": "IBM Corp."
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,33 +1,26 @@
|
||||||
// Copyright IBM Corp. 2015,2016. All Rights Reserved.
|
// Copyright IBM Corp. 2016,2017. All Rights Reserved.
|
||||||
// Node module: loopback-context-cls
|
// Node module: loopback-context
|
||||||
// This file is licensed under the MIT License.
|
// This file is licensed under the MIT License.
|
||||||
// License text available at https://opensource.org/licenses/MIT
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
var cls = require('cls-hooked');
|
||||||
var domain = require('domain');
|
var domain = require('domain');
|
||||||
|
|
||||||
// Require CLS only when using the current context feature.
|
|
||||||
// As soon as this require is done, all the instrumentation/patching
|
|
||||||
// of async-listener is fired which is not ideal.
|
|
||||||
//
|
|
||||||
// Some users observed stack overflows due to promise instrumentation
|
|
||||||
// and other people have seen similar things:
|
|
||||||
// https://github.com/othiym23/async-listener/issues/57
|
|
||||||
// It all goes away when instrumentation is disabled.
|
|
||||||
var cls = function() {
|
|
||||||
return require('continuation-local-storage');
|
|
||||||
};
|
|
||||||
|
|
||||||
var LoopBackContext = module.exports;
|
var LoopBackContext = module.exports;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the current context object. The context is preserved
|
* Get the current context object. The context is preserved
|
||||||
* across async calls, it behaves like a thread-local storage.
|
* across async calls, it behaves like a thread-local storage.
|
||||||
*
|
*
|
||||||
|
* @options {Object} [options]
|
||||||
|
* @property {Boolean} bind Bind get/set/bind methods of the context to the
|
||||||
|
* context that's current at the time getCurrentContext() is invoked. This
|
||||||
|
* can be used to work around 3rd party code breaking CLS context propagation.
|
||||||
* @returns {Namespace} The context object or null.
|
* @returns {Namespace} The context object or null.
|
||||||
*/
|
*/
|
||||||
LoopBackContext.getCurrentContext = function() {
|
LoopBackContext.getCurrentContext = function(options) {
|
||||||
// A placeholder method, see LoopBackContext.createContext() for the real version
|
// A placeholder method, see LoopBackContext.createContext() for the real version
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
@ -85,11 +78,30 @@ LoopBackContext.createContext = function(scopeName) {
|
||||||
process.context = process.context || {};
|
process.context = process.context || {};
|
||||||
var ns = process.context[scopeName];
|
var ns = process.context[scopeName];
|
||||||
if (!ns) {
|
if (!ns) {
|
||||||
ns = cls().createNamespace(scopeName);
|
ns = cls.createNamespace(scopeName);
|
||||||
process.context[scopeName] = ns;
|
process.context[scopeName] = ns;
|
||||||
// Set up LoopBackContext.getCurrentContext()
|
// Set up LoopBackContext.getCurrentContext()
|
||||||
LoopBackContext.getCurrentContext = function() {
|
LoopBackContext.getCurrentContext = function(options) {
|
||||||
return ns && ns.active ? ns : null;
|
if (!ns || !ns.active) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!options || !options.bind) {
|
||||||
|
return ns;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* **NOTE**
|
||||||
|
* This only re-binds get, set and bind methods, the most used.
|
||||||
|
* If you use other methods of the context, e.g. runInContext(), etc.,
|
||||||
|
* you may run into unexpected issues that are fixed only for get & set.
|
||||||
|
*/
|
||||||
|
var boundContext = Object.create(ns);
|
||||||
|
boundContext.get = boundContext.bind(ns.get);
|
||||||
|
boundContext.set = boundContext.bind(ns.set);
|
||||||
|
|
||||||
|
// Call to Function.prototype.bind(), not ns.bind()
|
||||||
|
boundContext.bind = ns.bind.bind(ns);
|
||||||
|
|
||||||
|
return boundContext;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return ns;
|
return ns;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
// Copyright IBM Corp. 2016. All Rights Reserved.
|
||||||
// Node module: loopback-context-cls
|
// Node module: loopback-context
|
||||||
// This file is licensed under the MIT License.
|
// This file is licensed under the MIT License.
|
||||||
// License text available at https://opensource.org/licenses/MIT
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
// Copyright IBM Corp. 2016. All Rights Reserved.
|
||||||
|
// Node module: loopback-context
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var chai = require('chai');
|
var chai = require('chai');
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
// Copyright IBM Corp. 2013,2016. All Rights Reserved.
|
// Copyright IBM Corp. 2016,2018. All Rights Reserved.
|
||||||
// Node module: loopback-context-cls
|
// Node module: loopback-context
|
||||||
// This file is licensed under the MIT License.
|
// This file is licensed under the MIT License.
|
||||||
// License text available at https://opensource.org/licenses/MIT
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
var asyncV152 = require('async-1.5.2');
|
||||||
|
var whenV377 = require('when-3.7.7');
|
||||||
var LoopBackContext = require('..');
|
var LoopBackContext = require('..');
|
||||||
var Domain = require('domain');
|
var Domain = require('domain');
|
||||||
var EventEmitter = require('events').EventEmitter;
|
var EventEmitter = require('events').EventEmitter;
|
||||||
|
@ -50,7 +52,7 @@ describe('LoopBack Context', function() {
|
||||||
TestModel.test = function(inst, cb) {
|
TestModel.test = function(inst, cb) {
|
||||||
var tmpCtx = LoopBackContext.getCurrentContext();
|
var tmpCtx = LoopBackContext.getCurrentContext();
|
||||||
if (tmpCtx) tmpCtx.set('data', 'a value stored in context');
|
if (tmpCtx) tmpCtx.set('data', 'a value stored in context');
|
||||||
if (process.domain) cb = process.domain.bind(cb); // IMPORTANT
|
if (process.domain) cb = process.domain.bind(cb); // IMPORTANT
|
||||||
runInOtherDomain(cb);
|
runInOtherDomain(cb);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -98,4 +100,150 @@ describe('LoopBack Context', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Credits for the original idea for this test case to @marlonkjoseph
|
||||||
|
// Original source of the POC gist of the idea:
|
||||||
|
// https://gist.github.com/marlonkjoseph/f42f3c71f746896a0d4b7279a34ea753
|
||||||
|
// Heavily edited by others
|
||||||
|
it('keeps context when using waterfall() from async 1.5.2',
|
||||||
|
function(done) {
|
||||||
|
LoopBackContext.runInContext(function() {
|
||||||
|
// Trigger async waterfall callbacks
|
||||||
|
asyncV152.waterfall([
|
||||||
|
function pushToContext(next) {
|
||||||
|
var ctx = LoopBackContext.getCurrentContext();
|
||||||
|
expect(ctx).is.an('object');
|
||||||
|
ctx.set('test-key', 'test-value');
|
||||||
|
next();
|
||||||
|
},
|
||||||
|
function pullFromContext(next) {
|
||||||
|
var ctx = LoopBackContext.getCurrentContext();
|
||||||
|
expect(ctx).is.an('object');
|
||||||
|
var testValue = ctx && ctx.get('test-key', 'test-value');
|
||||||
|
next(null, testValue);
|
||||||
|
},
|
||||||
|
function verify(testValue, next) {
|
||||||
|
expect(testValue).to.equal('test-value');
|
||||||
|
next();
|
||||||
|
},
|
||||||
|
], done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles concurrent then() calls with when v3.7.7 promises & bind option',
|
||||||
|
function() {
|
||||||
|
return Promise.all([
|
||||||
|
runWithPushedValue('test-value-1', {bind: true}),
|
||||||
|
runWithPushedValue('test-value-2', {bind: true}),
|
||||||
|
])
|
||||||
|
.then(function verify(values) {
|
||||||
|
var failureCount = getFailureCount(values);
|
||||||
|
expect(failureCount).to.equal(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails once without bind option and when v3.7.7 promises',
|
||||||
|
function() {
|
||||||
|
return Promise.all([
|
||||||
|
runWithPushedValue('test-value-3'),
|
||||||
|
runWithPushedValue('test-value-4'),
|
||||||
|
])
|
||||||
|
.then(function verify(values) {
|
||||||
|
var failureCount = getFailureCount(values);
|
||||||
|
expect(failureCount).to.equal(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var timeout = 100;
|
||||||
|
|
||||||
|
function runWithPushedValue(pushedValue, options) {
|
||||||
|
return new Promise(function concurrentExecutor(outerResolve, reject) {
|
||||||
|
LoopBackContext.runInContext(function pushToContext() {
|
||||||
|
var ctx = LoopBackContext.getCurrentContext(options);
|
||||||
|
expect(ctx).is.an('object');
|
||||||
|
ctx.set('test-key', pushedValue);
|
||||||
|
var whenPromise = whenV377().delay(timeout);
|
||||||
|
whenPromise.then(function pullFromContextAndReturn() {
|
||||||
|
var pulledValue = ctx && ctx.get('test-key');
|
||||||
|
outerResolve({
|
||||||
|
pulledValue: pulledValue,
|
||||||
|
pushedValue: pushedValue,
|
||||||
|
});
|
||||||
|
}).catch(reject);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFailureCount(values) {
|
||||||
|
var failureCount = 0;
|
||||||
|
values.forEach(function(v) {
|
||||||
|
if (v.pulledValue !== v.pushedValue) {
|
||||||
|
failureCount++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return failureCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
it('doesn\'t mix up req\'s in chains of ' +
|
||||||
|
'Express-middleware-like func\'s if next() cb is bound',
|
||||||
|
function() {
|
||||||
|
return Promise.all([
|
||||||
|
runWithRequestId('test-value-5', true),
|
||||||
|
runWithRequestId('test-value-6', true),
|
||||||
|
])
|
||||||
|
.then(function verify(values) {
|
||||||
|
var failureCount = getFailureCount(values);
|
||||||
|
expect(failureCount).to.equal(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails & mixes up ctx among requests in mw chains if next() cb is unbound',
|
||||||
|
function() {
|
||||||
|
return Promise.all([
|
||||||
|
runWithRequestId('test-value-7'),
|
||||||
|
runWithRequestId('test-value-8'),
|
||||||
|
])
|
||||||
|
.then(function verify(values) {
|
||||||
|
var failureCount = getFailureCount(values);
|
||||||
|
expect(failureCount).to.equal(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function runWithRequestId(pushedValue, bindNextCb) {
|
||||||
|
return new Promise(function chainExecutor(outerResolve, reject) {
|
||||||
|
LoopBackContext.runInContext(function concurrentChain() {
|
||||||
|
function middlewareBreakingCls(req, res, next) {
|
||||||
|
var ctx = LoopBackContext.getCurrentContext({bind: true});
|
||||||
|
if (bindNextCb) {
|
||||||
|
next = ctx.bind(next);
|
||||||
|
}
|
||||||
|
ctx.set('test-key', pushedValue);
|
||||||
|
var whenPromise = whenV377().delay(timeout);
|
||||||
|
whenPromise.then(next).catch(reject);
|
||||||
|
};
|
||||||
|
|
||||||
|
function middlewareReadingContext(req, res, next) {
|
||||||
|
var ctx = LoopBackContext.getCurrentContext({bind: true});
|
||||||
|
var pulledValue = ctx && ctx.get('test-key');
|
||||||
|
next(null, pulledValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Run the chain
|
||||||
|
var req = null;
|
||||||
|
var res = null;
|
||||||
|
middlewareBreakingCls(req, res, function(err) {
|
||||||
|
if (err) return reject(err);
|
||||||
|
|
||||||
|
middlewareReadingContext(req, res, function(err, result) {
|
||||||
|
if (err) return reject(err);
|
||||||
|
|
||||||
|
outerResolve({
|
||||||
|
pulledValue: result,
|
||||||
|
pushedValue: pushedValue,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
-r cls-hooked
|
|
@ -0,0 +1,7 @@
|
||||||
|
// Copyright IBM Corp. 2017. All Rights Reserved.
|
||||||
|
// Node module: loopback-context
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
module.exports = require('async');
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"name": "async-1.5.2",
|
||||||
|
"version": "1.5.2",
|
||||||
|
"description":"async version 1.5.2",
|
||||||
|
"dependencies": {
|
||||||
|
"async":"1.5.2"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
// Copyright IBM Corp. 2017. All Rights Reserved.
|
||||||
|
// Node module: loopback-context
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
module.exports = require('when');
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"name": "when-3.7.7",
|
||||||
|
"version": "3.7.7",
|
||||||
|
"description":"when version 3.7.7",
|
||||||
|
"dependencies": {
|
||||||
|
"when":"3.7.7"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue