添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
Cookie,Session和SSE—Nestjs文档更新(译)

Cookie,Session和SSE—Nestjs文档更新(译)

作为活跃的Nodejs开发框架,Nestjs处于快速迭代与更新中,在最新的Nestjs文档中,作者更新了Cookie,Session和SSE(服务器事件推送)内容,将安全部分单独成章并增加了权限、加密与哈希内容。更新的内容包括。

Cookies

一个 HTTP cookie 是指存储在用户浏览器中的一小段数据。 Cookies 被设计为创建一种可靠的机制让网站来记录状态信息。当用户再次访问网站时,发出的请求会自带 cookie

Express 中使用(默认)

首先安装需要的包(以及 TypeScript 用户需要的类型包):

$ npm i cookie-parser
$ npm i -D @types/cookie-parser

安装完成后,将 cookie-parser 配置为全局中间件(例如在 main.ts 文件中)。

import * as cookieParser from 'cookie-parser';
// somewhere in your initialization file
app.use(cookieParser());

可以向 cookieParser 中间件中传递一些参数:

  • secret : 一个字符串或者数组,用来给 cookie 签名。如果不指定这个选项,将不解析签名的 cookie 。如果提供了一个字符串,那么它会被用来作为 secret 。如果提供了一个数组,将尝试依次使用其元素来作为 secret 解析 cookie
  • option :一个作为第二个参数传递给 cookie.parse 的对象,参见 [cookie](https://www.npmjs.org/package/cookie) 来了解更多内容。
    该中间件将从请求的头文件中解析 Cookie 并将其数据作为 req.cookies 暴露出来。如果提供了 secret ,将暴露为 req.signedCokkies 。这些属性以 cookie 名称和属性的键值对保存。
    当提供了 secret 时,该中间件将解析并验证所有签名的 cookie 并将其值从 req.cookies 移动到 req.signedCookies 。签名 cookie 是指包含 s: 前缀的 cookie 。验证失败的签名 cookie 值会被替换为 false 而不是被篡改过的值。
    当这些完成后,就可以从路径处理程序中读取 cookie 了,例如:
@Get()
findAll(@Req() request: Request) {
  console.log(request.cookies); // or "request.cookies['cookieKey']"
  // or console.log(request.signedCookies);
}
@Req() 装饰器从 @nestjs/common 中引入, @Request express 中引入。
要在输出的响应中附加 cookie ,使用 Response#cookie() 方法:
@Get()
findAll(@Res({ passthrough: true }) response: Response) {
  response.cookie('key', 'value')
}
如果你想把相应处理逻辑留给框架,需要将 passthrough 参数设置为 true ,如上所示。参见 这里
@Res() 装饰器从 @nestjs/common 中引入, @Response express 中引入。

Fastify 中使用

首先安装需要的包:

npm i fastify-cookie
安装完成后,注册`fastify-cookie`插件。
```TypeScript
import fastifyCookie from 'fastify-cookie';
// somewhere in your initialization file
const app = await NestFactory.create<NestFastifyApplication>(
  AppModule,
  new FastifyAdapter(),
app.register(fastifyCookie, {
  secret: 'my-secret', // for cookies signature
});

当这些完成后,就可以从路径处理程序中读取 cookie 了,例如:

@Get()
findAll(@Req() request: FastifyRequest) {
  console.log(request.cookies); // or "request.cookies['cookieKey']"
}
@Req() 装饰器从 @nestjs/common 中引入, FastifyRequest fastify 中引入。
要在输出的响应中附加 cookie ,使用 FastifyReply#setCookie() 方法:
@Get()
findAll(@Res({ passthrough: true }) response: FastifyReply) {
  response.setCookie('key', 'value')
}

要了解更多 FastifyReply#setCookie() ,可参见 这里

如果你想把相应处理逻辑留给框架,需要将 passthrough 参数设置为 true ,如上所示。参见 这里
@Res() 装饰器从 @nestjs/common 中引入, FastifyReply fastify 中引入。

创建一个自定义装饰器(跨平台)

可以创建一个自定义装饰器,来提供一个方便易用、声明清晰的方式来处理 cookie

import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const Cookies = createParamDecorator(
  (data: string, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest();
    return data ? request.cookies?.[data] : request.cookies;
);

这个 @Cookie() 装饰器将解析所有 cookie ,或者从 req.cookie 对象中解析一个命名的 cookie 并用其值来填充解析的参数。

现在就可以使用该装饰器,从路径处理程序中读取 cookie 了,例如:

@Get()
findAll(@Cookies('name') name: string) {}

Session(会话)

HTTP session 提供了一个用于在不同请求间存储信息的方法,这在 MVC 架构应用中非常有用。

Express 中使用(默认)

首先安装需要的包(以及 TypeScript 用户需要的类型包):

$ npm i express-session
$ npm i -D @types/express-session

安装完成后,将 express-session 配置为全局中间件(例如在 main.ts 文件中)。

import * as session from 'express-session';
// somewhere in your initialization file
app.use(
  session({
    secret: 'my-secret',
    resave: false,
    saveUninitialized: false,
);
在生产环境中,有意的默认不在服务器端提供会话存储。因为这在很多场合下会造成内存泄漏,不能扩展到单个进程,因此仅用于调试和开发环境。参见 官方仓库
secret 用于加密该会话 ID cookie ,它可以是一个字符串用于单一加密,或者数组用来多重加密。如果提供了一个数组,只有第一个元素被用来加密会话 ID cookie ,其他元素将被用于验证签名请求。密码本身不应该过于容易被人工解析,最好使用一组随机字符。
使能 resave 选项会强制重新保存会话即使在请求过程中它未被修改过。其默认值为 true ,但不赞成使用默认值,在未来这个默认值将被修改。
类似地,使能 saveUninitialized 选项将强制存储一个未初始化的会话。一个未初始化的会话可能是一个新的尚未修改的会话。配置为 false 用于登陆会话是很有用的,可以减少服务器存储,或者遵循法律规定在存储用户 cookie 前需要获得用户授权。配置为 false 在一个客户端在无会话情况下建立多个请求的状况下会很有用。参见 这里
还可以给 session 中间件传递更多参数,参见 API 文档
注意 secure:true 是推荐选项。然而,它需要启用了 https 的网站,也就是说, HTTPS 对安全 cookie 来说是必须的。如果配置了 secure ,但是通过 HTTP 访问网站,将不会保存 cookie 。如果你的 node.js 在代理之后,并且启用了 secure:true 选项,你需要在 express 中配置 trust proxy 选项。
当这些完成后,就可以从路径处理程序中读取 session 了,例如:
@Get()
findAll(@Req() request: Request) {
  req.session.visits = req.session.visits ? req.session.visits + 1 : 1;
}
@Req() 装饰器从 @nestjs/common 中引入, @Request express 中引入。
另外,你也可以使用 @Session() 装饰器来从请求解压一个 session 对象:
@Get()
findAll(@Session() session: Record<string, any>) {
  session.visits = session.visits ? session.visits + 1 : 1;
}

Fastify 中使用

首先安装需要的包:

$ npm i fastify-secure-session
安装完成后,注册`fastify-secure-session`插件。
```TypeScript
import secureSession from 'fastify-secure-session';
// somewhere in your initialization file
const app = await NestFactory.create<NestFastifyApplication>(
  AppModule,
  new FastifyAdapter(),
app.register(secureSession, {
  secret: 'averylogphrasebiggerthanthirtytwochars',
  salt: 'mq9hDxBVDbspDR6n',
});
你也可以预先生成一个 key (参见 指南 )或者使用 key 变化
当这些完成后,就可以从路径处理程序中读取 session 了,例如:
@Get()
findAll(@Req() request: FastifyRequest) {
  const visits = request.session.get('visits');
  request.session.set('visits', visits ? visits + 1 : 1);
}
@Req() 装饰器从 @nestjs/common 中引入, FastifyRequest fastify 中引入。
另外,你也可以使用 @Session() 装饰器来从请求解压一个 session 对象:
@Get()
findAll(@Session() session: secureSession.Session) {
  const visits = session.get('visits');
  session.set('visits', visits ? visits + 1 : 1);
}
@Session() 装饰器从 @nestjs/common 中引入, secureSession.Session fastify-secure-session 中引入。(引入语句: import * as secureSession from 'fastify-secure-session' ).

事件

Event Emitter 事件发射器 包( @nestjs/event-emitter )提供了一个简单的观察者实现,允许你订阅和监听在你应用中发生的不同事件。事件服务器是将应用程序的不同部分解耦的一个伟大的方法,因为一个事件可以被不同的监听者监听,且他们之间并不互相依赖。

EventEmitterModule 在内部使用 eventmitter2 包。

开始

首先安装依赖。

$ npm i --save @nestjs/event-emitter

安装完成后,在 root AppModule 中引入 EventEmitterModule 并且在 forRoot() 静态方法中运行:

import { Module } from '@nestjs/common';
import { EventEmitterModule } from '@nestjs/event-emitter';
@Module({
  imports: [
    EventEmitterModule.forRoot()
export class AppModule {}

.forRoot() 调用与初始化事件发射器并注册应用程序中各个声明的事件监听器。注册发生在 onApplicationBootstrap 生命周期钩子开始的时候,以确保所有模块都已加载,所有定时任务已声明。

配置文件在 EventEmitter 实例中,在 forRoot() 方法中传递配置对象,如下:

EventEmitterModule.forRoot({
  // set this to `true` to use wildcards
  wildcard: false,
  // the delimiter used to segment namespaces
  delimiter: '.',
  // set this to `true` if you want to emit the newListener event
  newListener: false,
  // set this to `true` if you want to emit the removeListener event
  removeListener: false,
  // the maximum amount of listeners that can be assigned to an event
  maxListeners: 10,
  // show event name in memory leak message when more than maximum amount of listeners is assigned
  verboseMemoryLeak: false,
  // disable throwing uncaughtException if an error event is emitted and it has no listeners
  ignoreErrors: false,
});

分派事件

要分派一个事件(例如 fire ), 使用标准构造函数注入 EventEmitter2

constructor(private eventEmitter: EventEmitter2) {}
EventEmitter2 @nestjs/event-emitter 包中导入。
然后在类中使用它:
this.eventEmitter.emit(
  'order.created',
  new OrderCreatedEvent({
    orderId: 1,
    payload: {},
);

监听事件

要声明一个事件监听器,用 @OnEvent() 装饰器装饰一个方法,装饰器在包含要执行代码的方法之前,如下:

@OnEvent('order.created')
handleOrderCreatedEvent(payload: OrderCreatedEvent) {
  // handle and process "OrderCreatedEvent" event
}
事件订阅器不能是请求范围的。
在简单的事件发射器中,第一个参数可以是 字符串 或者 符号 ,在通配的事件发射器中,可以是 字符串|符号|数组<字符串|符号> 。第二个参数(可选的)是一个监听器的选项对象( 参见这里 )。
@OnEvent('order.created', { async: true })
handleOrderCreatedEvent(payload: OrderCreatedEvent) {
  // handle and process "OrderCreatedEvent" event
}

要使用命名空间或者通配符,传递 wildcard 选项到 EventEmitterModule#forRoot() 方法中。当命名空间/通配符启用时,事件可以是句点隔开的( foo.bar )形式或者数组( ['foo','bar'] ),句点也可以配置为一个配置属性( delimiter )。命名空间启用时,你可以使用通配符订阅事件。

@OnEvent('order.*')
handleOrderEvents(payload: OrderCreatedEvent | OrderRemovedEvent | OrderUpdatedEvent) {
  // handle and process an event
}

注意,这样的通配符仅对一个块有效。参数 order.* 将匹配例如 order.creted order.shipped 事件,但不会匹配 order.delayed.out_of_stock 。要监听这样的事件,使用 多层通配符 模式(例如 ** )。见 EventEmitter2 文档

@OnEvent('**')
handleEverything(payload: any) {
  // handle and process an event
}
EventEmitter2 类提供了一些有用的方法来和事件交互,例如 waitFor onAny ,参见 这里

服务器端事件发送

服务器端事件发送( SSE )是一个服务器推送技术,用来使客户端在 HTTP 连接下自动接收服务器更新消息。每个消息以一个由一对新行符号作为结束的文字块发送(参见 这里 )。

使用

要在路径中使能服务器端事件发送(路径在控制器类中注册),用 @Sse() 装饰器注释该方法处理程序。

@Sse('sse')
sse(): Observable<MessageEvent> {
  return interval(1000).pipe(map((_) => ({ data: { hello: 'world' } })));
}
@Sse() 装饰器从 @nestjs/common 中导入, Observable, interval, 和 map rxjs 中导入。
服务器端事件发送路径必须返回 Observable 流。
在上述示例中,我们定义了一个命名的 sse 来生成实时更新,这些事件可以通过 [EventSource API](https://developer.mozilla.org/en-US/docs/Web/API/EventSource) 监听。
sse 方法返回一个 Observable 并发送多个 MessageEvent (在本例中,它每秒发射一个新的 MessageEvent )。 MessageEvent 应该与下列接口相匹配。
export interface MessageEvent {
  data: string | object;
  id?: string;