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

发布于 1周前 作者 ionicwang 来自 nodejs/Nestjs

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

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

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

方法一

按照文档来

[@Post](/user/Post)('login')
async login ([@Body](/user/Body)() param,[@Res](/user/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](/user/Post)('login')
[@UseInterceptors](/user/UseInterceptors)(SetTokenInterceptor)
async login ([@Body](/user/Body)() param) {
 const data={}
 return {data,token:''}
 //问题在于怎么把 token 传给拦截器?
 //只能带在返回数据里,然后拦截器拿到 token 再删掉
}

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

所以还有其他方法吗


32 回复

老老实实用 express/koa,这风格一看就是 java 抄过来的,为什么要用 java 那套东西来复杂化 node.js


#1 我在这里被众人安利 nestjs 😂

token 直接放 json 返回咯

在后端给 response header 设置 token 没有任何意义

供参考:

(‘login’)
async login (Body() param,Res res) {
const {username, password} = param
const token =this.authService.validateUser(username, password)
return { token }
}

(’/profile’)
()
async user (CurrentUser() user) {
return this.userService.getUser(user.id)
}

实现一下 ()和 ()这两个注解即可

#4 怎么会没意义呢,我鉴权用 jwt 来做,就想 token 放在 response header 中给客户端去拿,极其常见的情况

另外你第一段代码试过了吗,你一旦用 这个装饰器后面 return 就没用了

那里是忘了删除,显然不需要 。

大家都是
客户端从 body 里拿到 token 保存到状态然后把 token 塞进每个请求里
你为什么要
客户端从 header 里挖 token 保存到状态然后把 token 塞进每个请求里


你自己造了个 token/set-token 方案?你都这样玩了为什么不用 set-cookie 呢?

#6

我也没啥特别的啊
仅仅仅就想 token 放 header 里面给客户端拿而已
为什么呢?因为客户端请求的时候会把 token 放在 header,所以为了对称美(狗头),下发 token 也想放 header 里面

#6 还有个好处忘了讲,比方 token 还剩 10 分种就过期了,客户端做其他业务请求的时候我想重新下发一个新 token 给他啊,放 header 里面就就非常合适

业务交互数据放 body
鉴权这种相对特殊的数据放 header

难道不是更常见?

#6 卧槽,还在想怎么实现这个 ,然后就受到启发,我可以自己写个装饰器自己拿到 response 啊

<br>export const resp = createParamDecorator((data, ctx: ExecutionContext) =&gt; {<br> return ctx.switchToHttp().getResponse()<br>})<br><br>('login')<br>async login (Body() param,resp() resp:any) {<br> const data={}<br> resp.set('token','')<br> return {data}<br>}<br>

非常非常完美!!!

10L 这样不也一样有副作用,要想最没副作用,那就这个方法就单独做参数处理器,return 出来后,用一个出口方法了 merge 到 resp 上,这样副作用才最小

Nest 的设计就不是无副作用的

#11 我说的副作用是指:只要用了 nestjs 提供的 装饰器后就不能直接 return 数据了,必须操作 res.json/send 去返回数据,然后这又导致拦截器不能正常工作

你说副作用具体是什么,还是说是指函数式编程中的那种副作用啊

嗯,我以为你说的是函数式编程的副作用

你用了 注解就会被忽略返回值啊, 这样避免重复操作流…
res.send 不就好了? 非要 return …

#16 我所有的控制器方法都是直接 return 数据,唯独这里不能 return,代码看起来我那个难受啊。。。

写个装饰器,装饰器可以拿到 req res,res 里设置下就行了,例如 (),,然后装饰器里面写逻辑,token 从哪里来

javascript<br>export const SetCookie = createParamDecorator(<br> (data: unknown, ctx: ExecutionContext) =&gt; {<br> const response: Response = ctx.switchToHttp().getResponse();<br> return function(data){<br> response.cookie('auth',data);<br> }<br> },<br>);<br>
参考

你这所谓“对称美”本来就是不应该的啊,我是有强迫症的人,我都不会这么考虑,因为不应该

登录接口响应的 token 是作为响应数据存在的,理应放在 body 里
需要鉴权的接口请求的 token 是作为鉴权依据存在的,理应放在 header 里

#20 你说的这个理我也认同

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

客户端怎么处理这种情况,当然是全局拦截器一发现 header 有 token 就保持起来,然后如果登录请求 token 放 body,那么客户端代码将有 2 处处理 token 的代码逻辑,如果登录 token 放 header,那么将简化为只有一处代码,不管是登录还是其他请求,只要 header 有 token 就保持起来,是不是更简明一点

#19 这跟我 10L 说的差不多

这里我的建议是,什么接口就做什么接口应该做的事情,处理 token 就要用专门处理 token 的接口,而不是在业务逻辑接口上插一个系统级的数据,这样会让人有种很脏的感觉

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

这样不是看起来很干净么?还是遵循那个原则:什么接口就做什么接口应该做的事情

#23 你这建议我觉得很 OK,接受,下个项目可以考虑这么干

目前来讲我是 token 还没过期,但是快过期了,比方一个 token 过期时间 30 天,那么最后 10%的时间,也就是 3 天,最后 3 天有请求就在 header 发个新 token 达到自动续签的目的,这样服务端还是客户端都能在一个地方统一处理 token 问题,要说个优点吧我就觉得这样特别简单轻松

#23 这个方法也是大多数网站的玩法,挺好的。楼主的方法让用户体验更好了,只要他在过期前有用过,就永远都是登录的,这特么不就是 cookie 吗。。。

对于用户体验来说都是一样的,都是无感刷新登录状态

#26 突然想到另外一个问题,并发的情况

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

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

请教下怎么考虑这种情况的

哦,我想到了,服务端 token 生成这个操作,独立出来,队列化处理吧

我记得 req 对象上挂载了 res 对象 所以…

可以看看类型声明
node_modules/nestjs/common/decorators/http/route-params.decorator.d.ts

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

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

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

拦截器也能生效

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

客户端也可以直接拿到返回

正解 👍

在 Node.js 中使用 NestJS 框架时,优雅地设置 HTTP 响应头(headers)通常通过中间件或控制器中的拦截器来实现。NestJS 提供了多种方式来管理响应头,这里介绍一种使用拦截器(Interceptor)的方法,因为这种方式更加模块化和可重用。

首先,你需要创建一个拦截器。以下是一个简单的示例,展示了如何设置自定义的响应头:

import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';

@Injectable()
export class CustomHeadersInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const response = context.switchToHttp().getResponse();
    response.setHeader('X-Custom-Header', 'CustomHeaderValue');
    return next.handle().pipe(
      tap(() => {
        // 可以在这里添加更多逻辑,比如日志记录等
      }),
    );
  }
}

然后,你可以将这个拦截器应用到整个应用、特定的控制器或特定的处理函数上。例如,将其应用到控制器上:

import { Controller, Get, UseInterceptors } from '@nestjs/common';
import { CustomHeadersInterceptor } from './custom-headers.interceptor';

@Controller('example')
@UseInterceptors(CustomHeadersInterceptor)
export class ExampleController {
  @Get()
  findAll() {
    return 'This action returns all items';
  }
}

这样,所有通过 ExampleController 处理的请求都会在响应中包含 X-Custom-Header。这种方式使得设置响应头变得既优雅又易于管理。

回到顶部