nestjs 如何优雅地给 response 设置 header?

2020-11-15 19:28:10 +08:00
 rikka

很常见的需求:一个登录请求过来后验证通过后要给 response 的 header 设置 token

我找到了 2 种方法但都不满意不优雅

方法一

按照文档来

@Post('login')
async login (@Body() param,@Res res) {
 const data={}
  res.set('token','')//这里设置 response 没问题
  //但是啊下面的 return 就无效了!!
  //你必须自己手动操作 res.json().send()去给客户端返回数据
  //还有副作用是拦截器不正常了
 return {data}
}

很难受,这种方法太怪胎了,还有副作用,弃之

方法二

用拦截器来帮忙设置 header

Injectable()
export class SetTokenInterceptor implements NestInterceptor {
  intercept (context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(tap(result => {
      if (result?.token) {
        const http = context.switchToHttp()
        const res = http.getResponse()
        res.set('token', result.token)
        delete result.token
        //这里要删掉 token,因为我不希望 token 返回到 respoonse 的 body 中
      }
    }))
  }
}
@Post('login')
@UseInterceptors(SetTokenInterceptor)
async login (@Body() param) {
 const data={}
 return {data,token:''}
 //问题在于怎么把 token 传给拦截器?
 //只能带在返回数据里,然后拦截器拿到 token 再删掉
}

这种方法也很难受,说不上具体,总之就是非常难受

所以还有其他方法吗

6915 次点击
所在节点    Node.js
31 条回复
rikka
2020-11-25 14:20:27 +08:00
@BoringTu #20 你说的这个理我也认同

但你考虑下这种情况:客户端做其他业务请求时,服务端发现 token 快过期了,要发个新的给客户端,新 token 放哪里?放 header 最恰当吧

客户端怎么处理这种情况,当然是全局拦截器一发现 header 有 token 就保持起来,然后如果登录请求 token 放 body,那么客户端代码将有 2 处处理 token 的代码逻辑,如果登录 token 放 header,那么将简化为只有一处代码,不管是登录还是其他请求,只要 header 有 token 就保持起来,是不是更简明一点
rikka
2020-11-25 14:21:59 +08:00
@hongweiliuruige #19 这跟我 10L 说的差不多
BoringTu
2020-11-25 14:52:09 +08:00
@rikka 这里我的建议是,什么接口就做什么接口应该做的事情,处理 token 就要用专门处理 token 的接口,而不是在业务逻辑接口上插一个系统级的数据,这样会让人有种很脏的感觉

比如你所说的场景,token 过期了,你发起一个业务逻辑的请求,服务器直接打回,响应了一个约定好的代表 token 过期的 code,前端接到这个响应并匹配了 code,就自动发起一个 refresh token 的请求
然后有两种情况:
1. 当新 token 正常响应了之后,前端自动发起之前的业务逻辑请求
2. 如果新 token 获取失败,前端直接踢到登录页

这样不是看起来很干净么?还是遵循那个原则:什么接口就做什么接口应该做的事情
rikka
2020-11-25 15:12:25 +08:00
@BoringTu #23 你这建议我觉得很 OK,接受,下个项目可以考虑这么干

目前来讲我是 token 还没过期,但是快过期了,比方一个 token 过期时间 30 天,那么最后 10%的时间,也就是 3 天,最后 3 天有请求就在 header 发个新 token 达到自动续签的目的,这样服务端还是客户端都能在一个地方统一处理 token 问题,要说个优点吧我就觉得这样特别简单轻松
galikeoy
2020-11-27 18:16:10 +08:00
@BoringTu #23 这个方法也是大多数网站的玩法,挺好的。楼主的方法让用户体验更好了,只要他在过期前有用过,就永远都是登录的,这特么不就是 cookie 吗。。。
BoringTu
2020-12-02 09:45:49 +08:00
@galikeoy 对于用户体验来说都是一样的,都是无感刷新登录状态
rikka
2020-12-02 12:12:33 +08:00
@BoringTu #26 突然想到另外一个问题,并发的情况

比如客户端同时发 2 个请求过来,服务端检查都快过期了,于是都重新生成 token 给回去了,虽然最终只会保存一个 token,也暂时没导致什么大问题,但这令我不爽了

你的方法好像也有同样的问题:2 个请求同时过来,服务端打回,于是同时发 2 个 refresh token 的请求

请教下怎么考虑这种情况的
rikka
2020-12-02 12:17:29 +08:00
哦,我想到了,服务端 token 生成这个操作,独立出来,队列化处理吧
cereschen
2020-12-16 19:27:44 +08:00
我记得 req 对象上挂载了 res 对象 所以....
ashe
260 天前
可以看看类型声明
node_modules/@nestjs/common/decorators/http/route-params.decorator.d.ts

```typescript
/**
* The `@Response()`/`@Res` parameter decorator options.
*/
export interface ResponseDecoratorOptions {
/**
* Determines whether the response will be sent manually within the route handler,
* with the use of native response handling methods exposed by the platform-specific response object,
* or if it should passthrough Nest response processing pipeline.
*
* @default false
*/
passthrough: boolean;
}
```

简单来说你的场景可以这样玩

```typescript
@Post("/world")
getHello(@Res({ passthrough: true }) response: Response) {
response.setHeader("x-hello", "world");
return { msg: "Hello World!" };
}
```

拦截器也能生效

```typescript
@Injectable()
export class RewritePost201To200Interruptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler) {
const host = context.switchToHttp();
const request: Request = host.getRequest();
const response: Response = host.getResponse();
return next.handle().pipe(
map((data: unknown) => {
if (response.statusCode === HttpStatus.CREATED && request?.method.toUpperCase() === "POST") {
response.status(HttpStatus.OK);
}
return data;
}),
);
}
}
```

客户端也可以直接拿到返回
dockerman
107 天前
@ashe 正解 👍

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/725512

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX