如何支持 "per request" 范围
How to support "per request" scope
我想从中间件中获取用户令牌并注入到控制器中,可以吗?
class TaskController {
@inject private currentUser
@Post('/tasks')
addTask() {
if (!hasPermission(currentUser)) throw new Error("Unauthorized.")
// ...
}
}
希望上面注入的currentUser可以通过一些中间件获取。
目前,InversifyJS 仅支持 singleton 和 trainsient 作用域。我们有一个 roadmap item 来研究新类型的作用域。
我们目前也在努力支持中间件,但还没有完全准备好。您可以通过 Gitter 联系开发团队,以了解有关我们计划的更多信息、帮助我们或请求功能。
InversifyJS 已经有一段时间了 inRequestScope()
,但它对 container-per-http-request 的情况没有多大帮助,因为 InversifyJS 的请求范围实际上与对 [=15 的单个调用相关联=],也就是说,每次调用 get
都被认为是一个请求,如果每个请求只调用一次 get
,它将仅按预期用于 HTTP 上下文。
我遇到了和你一样的问题:在一个中间件中,我需要从请求中提取当前用户并将该信息注入 CurrentUser
class,以便以后可以使用该信息被其他服务在线访问。
为此,我至少需要两次调用 get<CurrentUser>
:一个在中间件中,另一个用于为请求实例化 controller/handler。
所以,inRequestScope
不是一个可行的方案,inSingletonScope
或inTransientScope
也不行。
我最终创建了 ScopedContainer
class 你可以在下面看到。
首先,这是您的使用方式:
// register-global-dependencies.ts
ScopedContainer.globalContainer = (() => {
const container = new Container();
container
.bind<SomeSingletonDep>(TOKENS.SomeSingletonDep)
.to(SomeSingletonDep)
.inSingletonScope();
return container;
})();
☝️ 这允许你仍然有单例依赖。
// register-scoped-dependencies.ts
import "register-global-dependencies";
ScopedContainer.postConfigure((container) => {
container
.bind<RequestSpecificDep>(TOKENS.RequestSpecificDep)
.to(RequestSpecificDep)
.inSingletonScope();
});
☝️ 这控制了每个 HTTP 请求应解析一次的依赖项。
// lambda-handler.ts
import "register-scoped-dependencies";
handler = (event, context) => {
const requestId = event.requestContext.requestId;
const container = ScopedContainer.for(requestId);
try {
// This will be the same for every request
const singletonDep = container.get(TOKENS.SomeSingletonDep);
// And this will be a new instance for every request
const requestSpecificDep = container.get(TOKENS.RequestSpecificDep);
}
finally {
ScopedContainer.remove(requestId);
}
}
这是ScopedContainer
class:
import { Container, interfaces } from "inversify";
const DEFAULT_SCOPE_ID = "__default__";
type PostConfigureAction = (container: Container) => void;
type ScopedContainerCache = {
[id: string]: Container;
};
class ScopedContainer {
private static _postConfigureActions: PostConfigureAction[] = [];
private static readonly _instances: ScopedContainerCache = {};
/**
* Options object to use when creating a new container for a
* scope ID.
*/
static containerOptions: interfaces.ContainerOptions;
/**
* A global container instance, which enables truly
* singleton instances when using a scoped container. All scoped
* containers reference the global container as parent.
*/
static globalContainer: Container;
/**
* Returns a @see Container that is unique to the specified scope.
* If this is the first time getting the container for the scope, then a
* new container will be created using the provided factory. Any post configure
* actions will also be applied to the new container instance.
* @param scopeId Any string to identify the scope (e.g. current request ID).
* @returns A @see Container that is unique to the specified scope.
*/
static for(scopeId = DEFAULT_SCOPE_ID): Container {
let container = this._instances[scopeId];
if (!container) {
container = this.makeNewContainer();
this._instances[scopeId] = container;
}
return container;
}
/**
* Unbinds the @see Container (i.e. container.unbindAll()) and removes
* it from the cache.
* @param scopeId
*/
static remove(scopeId = DEFAULT_SCOPE_ID): void {
let container = this._instances[scopeId];
if (!container) return;
container.unbindAll();
delete this._instances[scopeId];
}
/**
* Runs the @method remove method on all instances.
*/
static removeAll(): void {
Object.keys(this._instances).forEach((key) => this.remove(key));
}
/**
* Adds a post configure action.
* @param fn A function that will be run everytime a new @see Container is created.
* @returns The @see ScopedContainer itself, to allow chaining.
*/
static postConfigure(fn: PostConfigureAction): ScopedContainer {
this._postConfigureActions.push(fn);
return this;
}
/**
* Removes any post configure actions.
*/
static resetPostConfigureActions(): void {
this._postConfigureActions = [];
}
private static makeNewContainer(): Container {
const container =
this.globalContainer?.createChild(this.containerOptions) ??
new Container(this.containerOptions);
this._postConfigureActions.forEach((action) => action(container));
return container;
}
}
export default ScopedContainer;
https://github.com/inversify/InversifyJS/issues/1076#issuecomment-1045179988
我想从中间件中获取用户令牌并注入到控制器中,可以吗?
class TaskController {
@inject private currentUser
@Post('/tasks')
addTask() {
if (!hasPermission(currentUser)) throw new Error("Unauthorized.")
// ...
}
}
希望上面注入的currentUser可以通过一些中间件获取。
目前,InversifyJS 仅支持 singleton 和 trainsient 作用域。我们有一个 roadmap item 来研究新类型的作用域。
我们目前也在努力支持中间件,但还没有完全准备好。您可以通过 Gitter 联系开发团队,以了解有关我们计划的更多信息、帮助我们或请求功能。
InversifyJS 已经有一段时间了 inRequestScope()
,但它对 container-per-http-request 的情况没有多大帮助,因为 InversifyJS 的请求范围实际上与对 [=15 的单个调用相关联=],也就是说,每次调用 get
都被认为是一个请求,如果每个请求只调用一次 get
,它将仅按预期用于 HTTP 上下文。
我遇到了和你一样的问题:在一个中间件中,我需要从请求中提取当前用户并将该信息注入 CurrentUser
class,以便以后可以使用该信息被其他服务在线访问。
为此,我至少需要两次调用 get<CurrentUser>
:一个在中间件中,另一个用于为请求实例化 controller/handler。
所以,inRequestScope
不是一个可行的方案,inSingletonScope
或inTransientScope
也不行。
我最终创建了 ScopedContainer
class 你可以在下面看到。
首先,这是您的使用方式:
// register-global-dependencies.ts
ScopedContainer.globalContainer = (() => {
const container = new Container();
container
.bind<SomeSingletonDep>(TOKENS.SomeSingletonDep)
.to(SomeSingletonDep)
.inSingletonScope();
return container;
})();
☝️ 这允许你仍然有单例依赖。
// register-scoped-dependencies.ts
import "register-global-dependencies";
ScopedContainer.postConfigure((container) => {
container
.bind<RequestSpecificDep>(TOKENS.RequestSpecificDep)
.to(RequestSpecificDep)
.inSingletonScope();
});
☝️ 这控制了每个 HTTP 请求应解析一次的依赖项。
// lambda-handler.ts
import "register-scoped-dependencies";
handler = (event, context) => {
const requestId = event.requestContext.requestId;
const container = ScopedContainer.for(requestId);
try {
// This will be the same for every request
const singletonDep = container.get(TOKENS.SomeSingletonDep);
// And this will be a new instance for every request
const requestSpecificDep = container.get(TOKENS.RequestSpecificDep);
}
finally {
ScopedContainer.remove(requestId);
}
}
这是ScopedContainer
class:
import { Container, interfaces } from "inversify";
const DEFAULT_SCOPE_ID = "__default__";
type PostConfigureAction = (container: Container) => void;
type ScopedContainerCache = {
[id: string]: Container;
};
class ScopedContainer {
private static _postConfigureActions: PostConfigureAction[] = [];
private static readonly _instances: ScopedContainerCache = {};
/**
* Options object to use when creating a new container for a
* scope ID.
*/
static containerOptions: interfaces.ContainerOptions;
/**
* A global container instance, which enables truly
* singleton instances when using a scoped container. All scoped
* containers reference the global container as parent.
*/
static globalContainer: Container;
/**
* Returns a @see Container that is unique to the specified scope.
* If this is the first time getting the container for the scope, then a
* new container will be created using the provided factory. Any post configure
* actions will also be applied to the new container instance.
* @param scopeId Any string to identify the scope (e.g. current request ID).
* @returns A @see Container that is unique to the specified scope.
*/
static for(scopeId = DEFAULT_SCOPE_ID): Container {
let container = this._instances[scopeId];
if (!container) {
container = this.makeNewContainer();
this._instances[scopeId] = container;
}
return container;
}
/**
* Unbinds the @see Container (i.e. container.unbindAll()) and removes
* it from the cache.
* @param scopeId
*/
static remove(scopeId = DEFAULT_SCOPE_ID): void {
let container = this._instances[scopeId];
if (!container) return;
container.unbindAll();
delete this._instances[scopeId];
}
/**
* Runs the @method remove method on all instances.
*/
static removeAll(): void {
Object.keys(this._instances).forEach((key) => this.remove(key));
}
/**
* Adds a post configure action.
* @param fn A function that will be run everytime a new @see Container is created.
* @returns The @see ScopedContainer itself, to allow chaining.
*/
static postConfigure(fn: PostConfigureAction): ScopedContainer {
this._postConfigureActions.push(fn);
return this;
}
/**
* Removes any post configure actions.
*/
static resetPostConfigureActions(): void {
this._postConfigureActions = [];
}
private static makeNewContainer(): Container {
const container =
this.globalContainer?.createChild(this.containerOptions) ??
new Container(this.containerOptions);
this._postConfigureActions.forEach((action) => action(container));
return container;
}
}
export default ScopedContainer;
https://github.com/inversify/InversifyJS/issues/1076#issuecomment-1045179988