五、重定向、响应状态码、响应头

这一节一起说一下@Redirect, @HttpCode, @Header这三个装饰器,这都是方法装饰器,用来装饰路由处理程序

前置内容

@Redirect用于重定向,有两种用法:

  • 静态的重定向地址@Redirect('url', 'httpCode')
  • 动态的重定向地址,在处理程序中返回{url: '', httpCode: number}

@HttpCode用来强制设置一个响应状态码

@Header用来设置响应头,可以设置多次,每次设置一组key-value,我们之前实现过@Headers装饰器,那是一个参数装饰器,用来获取请求头,这里是@Header,用来设置响应头,不要混淆

先看一下我们的几个新的路由:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// users.controller.ts
// 固定重定向地址
@Get('/redirect')
@Redirect('/users/req', 301)
handleRedirect() {}

// 动态生成重定向地址 - 在函数内部返回url和statusCode
@Get('/redirect-dynamic')
@Redirect('/users/req', 301)
handleRedirectDynamic(@Query('version') version) {
return {url: `https://docs.nestjs.com/${version}`, statusCode: 301}
}

// 强制设置状态码
@Get('code')
@HttpCode(201)
handleCode() {
return 'handleCode'
}

// 设置相应头
@Get('response-header')
@Header('Cache-Control', 'none')
@Header('key1', 'value1')
handleResponseHeader() {
return 'handleResponseHeader'
}

实现

我们在http-methods中实现这几个装饰器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// @nestjs/common/http-methods.decorator.ts

export function Redirect(url:string = '/', code: number = 302): MethodDecorator {
return (target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
// 给修饰的方法添加元数据
Reflect.defineMetadata('redirectUrl', url, descriptor.value)
Reflect.defineMetadata('redirectStatusCode', code, descriptor.value)
}
}

export function HttpCode(code: number): MethodDecorator {
return (target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
// 给修饰的方法添加元数据
Reflect.defineMetadata('httpCode', code, descriptor.value)
}
}

export function Header(name: string, value: string): MethodDecorator {
return (target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
// 响应头会设置多个,所以不能直接存一个key-value,而是保存为一个数组
const existedHeaders = Reflect.getMetadata('responseHeaders', descriptor.value) || []
existedHeaders.push({
name,
value
})
Reflect.defineMetadata('responseHeaders', existedHeaders, descriptor.value)
}
}

NestApplication中,要解析他们,这里不是参数装饰器,自然不需要加什么新的Case了,而是在init方法中做了相应的处理,因为init方法的内容目前比较多,所以用~~~~~标注了修改的地方,注释比较全,看代码即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// @nestjs/core/nest-application.ts
async init() {
// 取出模块里的所有控制器,做好路由配置
// 传入的模块及其对应的控制器等内容已经执行了装饰器,所以元数据已经定义好了
const controllers = Reflect.getMetadata('controllers', this.module) || []
Logger.log(`AppModule dependencies initialized`, 'InstanceLoader')
// 遍历控制器
for (const Controller of controllers) {
// 创建每个控制器的实例
const controller = new Controller()
// 获取控制器的路径前缀, 无前缀则默认'/'
const prefix = Reflect.getMetadata('prefix', Controller) || '/'
Logger.log(`${Controller.name} {${prefix}}`, 'RoutesResolver')
// 遍历类里的方法
const controllerPrototype = Reflect.getPrototypeOf(controller)
for (const methodName of Object.getOwnPropertyNames(controllerPrototype)){
// 取方法
const method = controllerPrototype[methodName]
// 取请求方法
const httpMethod = Reflect.getMetadata('method', method)
// 取路径
const httpPath = Reflect.getMetadata('path', method)
~~~~~~~~~~~~~~~~~~~~~~~~~
// 取重定向地址和code
const redirectUrl = Reflect.getMetadata('redirectUrl', method)
const redirectStatusCode = Reflect.getMetadata('redirectStatusCode', method)
// 取响应状态码
const httpCode = Reflect.getMetadata('httpCode', method)
// 取响应头
const responseHeaders = Reflect.getMetadata('responseHeaders', method) || []
if (!httpMethod) continue;
// 拼接路由
const routePath = path.posix.join('/', prefix, httpPath)
// 注册路由 - express上通过app.get('/path', () => {})来注册路由
this.app[httpMethod.toLowerCase()](routePath, (req: Request, res: Response, next: NextFunction) => {
// 解析参数
const args = this.resolveParams(controller, methodName, req, res, next)
// 交由对应方法来处理请求
const result = method.call(controller, ...args)
// 如果有重定向url,则需要进行重定向,就不用返回响应了
// 如果处理程序返回了重定向的url,则使用这个动态的url,否则使用传入装饰器@Redirect的url
if (redirectUrl) {
res.redirect(result?.statusCode ?? (redirectStatusCode || 302), result?.url ?? redirectUrl)
}
// 如果使用了@HttpCode,则使用指定的状态码
// 如果是POST请求,默认应该是201
if (httpCode) {
res.statusCode = httpCode
} else if (httpMethod === 'POST') {
res.statusCode = 201
}
// 如果注入了响应头,则需要设置
if (responseHeaders.length) {
responseHeaders.forEach(({name, value}) => {
res.setHeader(name, value)
})
}
~~~~~~~~~~~~~~~~~~~~~~~~~
// 如果注入了@Response,且没有传递@Response({passthrough=true}),则需要自己控制响应, Nestjs不会返回客户端任何内容
// 如果注入了@Next,也就是自己控制是否继续执行到后边的中间件,那么也要自己控制响应,Nestjs不会返回任何内容
// 所以需要判断是否需要将结果自动返回给客户端
const autoResponse = this.ifAutoResponse(controller, methodName)
if (autoResponse) {
res.send(result)
}
})
Logger.log(`Mapped {${routePath}, ${httpMethod}} route`, 'RoutesResolver')
}
}
Logger.log('Nest application successfully started', 'NestApplication')
}
作者

胡兆磊

发布于

2025-01-08

更新于

2025-04-21

许可协议