FAQ
HTTP 适配器
有时,您可能希望在 Nest
应用程序上下文中或从外部访问底层 HTTP
服务器。
基本上,每个本机(特定于平台的)HTTP
服务器/库实例都包含在 adapter
(适配器)中。适配器注册为全局可用的提供程序,可以从应用程序上下文中提取,也可以轻松地注入其他提供程序。
外部应用上下文策略
为了从应用程序上下文外部获取 HttpAdapter
引用,您可以调用 getHttpAdapter()
方法。
const app = await NestFactory.create(ApplicationModule);
const httpAdapter = app.getHttpAdapter();
上下文策略
为了从应用程序上下文中获取HttpAdapterHost
引用,您可以采用与任何其他现有提供程序相同的方式注入它(例如,通过 constructor
注入)。
export class CatsService {
constructor(private adapterHost: HttpAdapterHost) {}
}
!> HttpAdapterHost
需要从 @nestjs/core
导入包。
HttpAdapterHost
不是真实的 HttpAdapter
。为了获得 HttpAdapter
,只需访问该 httpAdapter
属性。
const adapterHost = app.get(HttpAdapterHost);
const httpAdapter = adapterHost.httpAdapter;
该 httpAdapter
是底层框架使用的 HTTP
适配器的实际实例。它可以是 ExpressAdapter
或 FastifyAdapter
的实例(两个类都扩展了自AbstractHttpAdapter
)。
每个适配器都公开了几种与 HTTP
服务器交互的有用方法。尽管如此,如果您想直接访问库引用,请调用 getInstance()
方法。
const instance = httpAdapter.getInstance();
全局路由前缀
要为应用程序中的每个路由设置前缀, 让我们使用 INestApplication
对象的 setGlobalPrefix()
方法。
const app = await NestFactory.create(ApplicationModule);
app.setGlobalPrefix('v1');
混合应用
混合应用程序是一个应用程序,它监听 HTTP
请求,可以通过 connectMicroservice()
函数将 INestApplication
实例与 INestMicroservice
实例结合起来。
const app = await NestFactory.create(ApplicationModule);
const microservice = app.connectMicroservice({
transport: Transport.TCP,
});
await app.startAllMicroservicesAsync();
await app.listen(3001);
要连接多个微服务实例,要为每个微服务调用connectMicroservice()
方法:
const app = await NestFactory.create(AppModule);
// microservice #1
const microserviceTcp = app.connectMicroservice<MicroserviceOptions>({
transport: Transport.TCP,
options: {
port: 3001,
},
});
// microservice #2
const microserviceRedis = app.connectMicroservice<MicroserviceOptions>({
transport: Transport.REDIS,
options: {
url: 'redis://localhost:6379',
},
});
await app.startAllMicroservicesAsync();
await app.listen(3001);
HTTPS 和多服务器
HTTPS
为了创建使用 HTTPS
协议的应用程序,在传递给NestFactory
的create()
方法中设置httpsOptions
属性:
const httpsOptions = {
key: fs.readFileSync('./secrets/private-key.pem'),
cert: fs.readFileSync('./secrets/public-certificate.pem'),
};
const app = await NestFactory.create(ApplicationModule, {
httpsOptions,
});
await app.listen(3000);
如果使用FastifyAdapter
,则创建应用如下:
const app = await NestFactory.create<NestFastifyApplication>(
ApplicationModule,
new FastifyAdapter({ https: httpsOptions }),
);
多个同步服务器
下列方法展示了如何使用Nest应用同时监视多个端口(例如,在非HTTPS端口和HTTPS端口)。
const httpsOptions = {
key: fs.readFileSync('./secrets/private-key.pem'),
cert: fs.readFileSync('./secrets/public-certificate.pem'),
};
const server = express();
const app = await NestFactory.create(
ApplicationModule,
new ExpressAdapter(server),
);
await app.init();
http.createServer(server).listen(3000);
https.createServer(httpsOptions, server).listen(443);
?> ExpressAdapter
需要从 @nestjs/platform-express
包导入。http
和https
包是原生的Node.js包。
!> GraphQL Subscriptions
中该方法无法工作。
请求生命周期
Nest
应用程序处理请求并生成回应的过程被称为请求生命周期。使用中间件、管道、守卫和拦截器时,要在请求生命周期中追踪特定的代码片段的执行很困难,尤其是在全局、控制器或者路由的部件中。一般来说,一个请求流经中间件、守卫与拦截器,然后到达管道,并最终回到拦截器中的返回路径中(从而产生响应)。
中间件
中间件以特殊的顺序执行。首先,Nest
运行全局绑定的中间件(例如app.use
中绑定的中间件),然后运行在路径中指定的模块绑定的中间件。中间件以他们绑定的次序顺序执行,这和在Express
中的中间件工作原理是类似的。
守卫
守卫的执行首先从全局守卫开始,然后处理控制器守卫,最后是路径守卫。和中间件一样,守卫的执行也和他们的绑定顺序一致。例如:
@UseGuards(Guard1, Guard2)
@Controller('cats')
export class CatsController {
constructor(private catsService: CatsService) {}
@UseGuards(Guard3)
@Get()
getCats(): Cats[] {
return this.catsService.getCats();
}
}
Guard1
会在Guard2
之前执行并且这两者都先于Guard3
执行。
?> 在提到全局绑定和本地绑定时主要是指守卫(或其他部件)绑定的位置不同。如果你正在使用app.useGlobalGuard()
或者通过模块提供一个部件,它就是全局绑定的。否则,当一个装饰器在控制器类之前时,它就是绑定在控制器上的,当装饰器在路径声明之前时它就是绑定在路径上的。
拦截器
拦截器在大部分情况下和守卫类似。只有一种情况例外:当拦截器返回的是一个RxJS Observables
时,observables
是以先进后出的顺序执行的。因此,入站请求是按照标准的全局、控制器和路由层次执行的,但请求的响应侧(例如,当从一个控制器方法的处理器返回时)则是从路由到控制器再到全局。另外,由管道、控制器或者服务抛出的任何错误都可以在拦截器的catchError
操作者中被读取。
管道
管道按照标准的从全局到控制器再到路由的绑定顺序,遵循先进先出的原则按照@usePipes()
参数次序顺序执行。然而,在路由参数层次,如果由多个管道在执行,则会按照自后向前的参数顺序执行,这在路由层面和控制器层面的管道中同样如此,例如,我们有如下控制器:
@UsePipes(GeneralValidationPipe)
@Controller('cats')
export class CatsController {
constructor(private catsService: CatsService) {}
@UsePipes(RouteSpecificPipe)
@Patch(':id')
updateCat(
@Body() body: UpdateCatDTO,
@Param() params: UpdateCatParams,
@Query() query: UpdateCatQuery,
) {
return this.catsService.updateCat(body, params, query);
}
}
在这里GeneralValidationPipe
会先执行query
,然后是params
,最后是body
对象,接下来在执行RouteSpecificPipe
管道时同样按照上述次序执行。如果存在任何参数层的管道,它会在(同样的,按照自后向前的参数顺序)控制器和路由层的管道之后执行。
过滤器
过滤器是唯一一个不按照全局第一顺序执行的组件。而是会从最低层次开始处理,也就是说先从任何路由绑定的过滤器开始,然后是控制器层,最后才是全局过滤器。注意,异常无法从过滤器传递到另一个过滤器;如果一个路由层过滤器捕捉到一个异常,一个控制器或者全局层面的过滤器就捕捉不到这个异常。如果要实现类似的效果可以在过滤器之间使用继承。
?> 过滤器仅在请求过程中任何没有捕获的异常发生时执行。捕获的异常如try/catch
语句不会触发过滤器。一旦遇到未处理的异常,请求接下来的生命周期会被忽略并直接跳转到过滤器。
总结
一般来说,请求生命周期大致如下:
- 收到请求
- 全局绑定的中间件
- 模块绑定的中间件
- 全局守卫
- 控制层守卫
- 路由守卫
- 全局拦截器(控制器之前)
- 控制器层拦截器 (控制器之前)
- 路由拦截器 (控制器之前)
- 全局管道
- 控制器管道
- 路由管道
- 路由参数管道
- 控制器(方法处理器) 15。服务(如果有)
- 路由拦截器(请求之后)
- 控制器拦截器 (请求之后)
- 全局拦截器 (请求之后)
- 异常过滤器 (路由,之后是控制器,之后是全局)
- 服务器响应
实例
译者署名
用户名 | 头像 | 职能 | 签名 | ||||
---|---|---|---|---|---|---|---|
@zuohuadong | 翻译 | 专注于 caddy 和 nest,@zuohuadong at Github | |||||
@Drixn | 翻译 | 专注于 nginx 和 C++,@Drixn | @Drixn | 翻译 | 专注于 nginx 和 C++,@Drixn | ||
@Armor | 翻译 | 专注于 Java 和 Nest,@Armor | |||||
@weizy0219 | 翻译 | 专注于TypeScript全栈、物联网和Python数据科学,@weizhiyong |