请求处理

请求处理

控制器负责处理传入的请求和向客户端返回响应。

Nest Controllers

控制器的目的是接收应用的特定请求。路由机制控制哪个控制器接收哪些请求。通常,每个控制器有多个路由,不同的路由可以执行不同的操作。为了创建一个基本的控制器,我们必须使用装饰器。装饰器将类与所需的元数据关联,并使 Nest 能够创建路由映射(将请求绑定到相应的控制器)。

路由

得益于 TypeScript,在 Nest 中我们可以使用类来实现控制器的功能,使用装饰器来实现路由功能。它们分别需要配合 @Controller 和 @Get 饰器来使用,前者是控制器类的装饰,后者是具体方法的装饰器。比如下面的代码:

import { Controller, Get } from "@nestjs/common";

@Controller("cats")
export class CatsController {
  @Get()
  findAll(): string {
    return "This action returns all cats";
  }
}

我们可以用 $ nest g controller cats 来自动生成上面的代码。@Get() 表示 HTTP 请求装饰器。控制器类的装饰器和 HTTP 方法的装饰器共同决定了一个路由规则。findAll 将返回一个状态码为 200 的响应,当然你有两种方法来指定返回的状态码:

  • 标准模式(建议的):使用内置方法时,如果返回一个 JavaScript 对象或者数据,将自动序列化成 JSON,如果是字符串将默认不会序列化,响应的返回状态码 默认 总是 200,除非是 POST 请求会默认设置成 201。可以使用 @HttpCode() 装饰器来改变它

  • 指定框架:也可以使用指定框架的请求处理方法,比如 Express 的响应对象。可以使用 @Res() 装饰器来装饰响应对象使用,这样以来你就可以使用类 Express API 的方式处理响应了:response.status(200).send()

路由的注册顺序与控制器类中的方法顺序相关,如果你先装饰了一个 cats/:id 的路由,后面又装饰了一个 cats 路由,那么当用户访问到 GET /cats 时,后面的路由将不会被捕获,因为参数才都是非必选的。

路由通配符

Nest 支持基于模式的路由规则匹配,比如:星号(*)表示匹配任意的字母组合。

@Get('ab*cd')

The 'ab*cd' 路由将匹配 abcd, ab_cd, abecd 等规则。同时:?, +, *, and () 通配符(wildcard)都可以使用。

通配符 说明 示例 匹配 不匹配
* 匹配任意数量的任意字符 Law* Law, Laws, or Lawyer GrokLaw, La, or aw
*Law* Law, GrokLaw, or Lawyer. La, or aw
? 匹配任意单个字符 ?at Cat, cat, Bat or bat at
[abc] 匹配方括号中的任意一个字符 [CB]at Cat or Bat cat or bat
[a-z] 匹配字母、数字区间 Letter[0-9] Letter0, Letter1, Letter2 up to Letter9 Letters, Letter or Letter10

路由参数

通常我们需要设置一些动态的路由来接收一些客户端的查询参数,通过指定路由参数可以很方便的捕获到 URL 上的动态参数到控制器中。

@Get(':id')
findOne(@Param() params): string {
  console.log(params.id);
  return `This action returns a #${params.id} cat`;
}

通过使用 @Param() 装饰器可以在方法中直接访问到路由装饰器 @Get() 中的的参数字典,:id 就表示匹配到所有的字符串,可以通过引用 params.id 在方法中访问到。

当然,就像前面学到的参数装饰器也可以指定到具体的某个参数值:

@Get(':id')
findOne(@Param('id') id): string {
  return `This action returns a #${id} cat`;
}

请求对象

处理器一般需要访问到请求对象。一般配合 @Req() 装饰器来使用,请求对象包含查询字符串、参数、HTTP 头,请求体等。但是大多数情况只用到其中某个,我们可以单独使用指定的装饰器来装饰请求。

装饰器 参数值
@Request() req
@Response() res
@Next() next
@Session() req.session
@Param(key?: string) req.params / req.params[key]
@Body(key?: string) req.body / req.body[key]
@Query(key?: string) req.query / req.query[key]
@Headers(name?: string) req.headers / req.headers[name]

譬如,我们只需要处理请求的查询字符串(query string),就可以使用 @Query 来装饰入参,这样取到的值就自然是一个 query string 的字典了。

@Get()
getHello(@Query() q: String): string {
    console.log(q)
    return this.appService.getHello();
}
// http://localhost:3000/?test=a

除了使用 @Get 装饰器,我们还可以使用其它 HTTP 方法装饰器。比如:@Put(), @Delete(), @Patch(), @Options(), @Head(), and @All(),注意 All 并不是 HTTP 的方法,而是 Nest 提供的一个快捷方式,表示接收任何类型的 HTTP 请求。

请求体

现在我们习惯以 JSON 请求体的方式传递参数(当然也可以以传统的 Form 方式提交参数),Nest 内置了 Body 注解,可以将请求体直接转化为 DTO 实体类。譬如我们声明了请求体关联的 DTO:

import { IsInt, IsString } from "class-validator";

export class CreateCatDto {
  @IsString()
  readonly name: string;

  @IsInt()
  readonly age: number;

  @IsString()
  readonly breed: string;
}

然后在请求函数中我们可以直接获取该对象:

@Post()
@Roles('admin')
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}

响应对象

状态码

响应的默认状态码是 200,POST 则是 201,我们可以使用装饰器 @HttpCode(204) 来指定处理器级别的 默认 HttpCode 为 204。

@Post()
@HttpCode(204)
create() {
  return 'This action adds a new cat';
}

如果想动态指定状态码,就要使用 @Res() 装饰器来注入响应对象,同时调用响应的状态码设置方法。

响应头

同样的我们可以使用 @Header() 来设置自定义的请求头,也可以使用 response.header() 设置。

@Post()
@Header('Cache-Control', 'none')
create() {
  return 'This action adds a new cat';
}

返回文件流

在 Nest 中我们可以直接将某个流返回给响应:

// This is my controller
async getFile(@Param('bucketname') bucketName: string, @Param('filename') fileName: string, @Res() response) {
  return (await this.appService.getFile(bucketName, fileName)).pipe(response);
}

这里以常见的打印 PDF 的需求为例:

// Controller
import { Post, Body, Res } from '@nestjs/common';
import { Response } from 'express';


@Post('pdf')
async print(
  @Body(new JoiValidationPipe(printSchema)) body: PrintDto,
  @Res() res: Response,
) {
  const buffer = await this.printService.printPdf(body);
  const stream = this.printService.getReadableStream(buffer);

  res.set({
    'Content-Type': 'application/pdf',
    'Content-Length': buffer.length,
  });

  stream.pipe(res);
}

// PrintService
import { Readable } from 'stream';
// import PrintDto

async printPdf(body: PrintDto): Promise<Buffer> {
  // ...
  return buffer;
}

getReadableStream(buffer: Buffer): Readable {
  const stream = new Readable();

  stream.push(buffer);
  stream.push(null);

  return stream;
}
上一页
下一页