【问题标题】:How do I pass plain text as my request body using NestJS?如何使用 NestJS 将纯文本作为我的请求正文传递?
【发布时间】:2019-02-16 10:02:24
【问题描述】:

我的 NestJS 应用程序中的一个控制器方法应该将纯文本作为其主体,但每当我尝试发出请求时,参数都会作为空对象接收。这甚至可能吗,还是我必须创建某种 DTO 来传递该单个字符串?

例子:

@Post()
  myFunction(@Body() id: string) {
    // do something here
  }

【问题讨论】:

  • 您传递的内容类型是否有效?
  • 我在 Postman 中尝试过 "text" 和 "text/plain",但它们都没有被 Nest 作为字符串拾取。

标签: javascript typescript http postman nestjs


【解决方案1】:

如果您希望避免额外的第 3 方依赖项,您还可以在此处使用 built-in nodejs 方法:

function readPost(req: IncomingMessage) {
  return new Promise<string>((resolve, reject) => {
    let body = '';
    req.on('data', (data: string) => (body += data));
    req.on('error', (error: unknown) => reject(error));
    req.on('end', () => resolve(body));
  });
}

用法:

import { Post, Req } from '@nestjs/common';
import { IncomingMessage } from 'http';
...
@Post()
myFunction(@Req() req: IncomingMessage) {
  const bodyStr = await readPost(req);
  console.log('request body:', bodyStr);
}

【讨论】:

    【解决方案2】:

    这是我对在 NestJS 的处理程序中获取原始(文本)正文的看法:

    1. 使用preserveRawBodyInRequest 配置应用程序,如 JSDoc 示例所示
    2. 在处理程序中使用 RawBody 装饰器来检索原始(文本)正文

    原始请求.decorator.ts:

    import { createParamDecorator, ExecutionContext } from '@nestjs/common';
    import { NestExpressApplication } from "@nestjs/platform-express";
    
    import { json, urlencoded } from "express";
    import type { Request } from "express";
    import type http from "http";
    
    export const HTTP_REQUEST_RAW_BODY = "rawBody";
    
    /**
     * make sure you configure the nest app with <code>preserveRawBodyInRequest</code>
     * @example
     * webhook(@RawBody() rawBody: string): Record<string, unknown> {
     *   return { received: true };
     * }
     * @see preserveRawBodyInRequest
     */
    export const RawBody = createParamDecorator(
      async (data: unknown, context: ExecutionContext) => {
        const request = context
          .switchToHttp()
          .getRequest<Request>()
        ;
    
        if (!(HTTP_REQUEST_RAW_BODY in request)) {
          throw new Error(
            `RawBody not preserved for request in handler: ${context.getClass().name}::${context.getHandler().name}`,
          );
        }
    
        const rawBody = request[HTTP_REQUEST_RAW_BODY];
    
        return rawBody;
      },
    );
    
    /**
     * @example
     * const app = await NestFactory.create<NestExpressApplication>(
     *   AppModule,
     *   {
     *     bodyParser: false, // it is prerequisite to disable nest's default body parser
     *   },
     * );
     * preserveRawBodyInRequest(
     *   app,
     *   "signature-header",
     * );
     * @param app
     * @param ifRequestContainsHeader
     */
    export function preserveRawBodyInRequest(
      app: NestExpressApplication,
      ...ifRequestContainsHeader: string[]
    ): void {
      const rawBodyBuffer = (
        req: http.IncomingMessage,
        res: http.ServerResponse,
        buf: Buffer,
      ): void => {
        if (
          buf?.length
          && (ifRequestContainsHeader.length === 0
            || ifRequestContainsHeader.some(filterHeader => req.headers[filterHeader])
          )
        ) {
          req[HTTP_REQUEST_RAW_BODY] = buf.toString("utf8");
        }
      };
    
      app.use(
        urlencoded(
          {
            verify: rawBodyBuffer,
            extended: true,
          },
        ),
      );
      app.use(
        json(
          {
            verify: rawBodyBuffer,
          },
        ),
      );
    }
    

    【讨论】:

      【解决方案3】:

      老问题,但以上都没有对我有用,但以下对我有用:

      上述装饰器或控制器方法方法对我不起作用,因为请求正文缓冲区始终已被读取。

      我能够使用以下中间件使其正常工作。 (请注意,在我的情况下,我需要验证 Xero webhook,因此该示例适用于此)

      cache-raw-body-on-request.ts:

      import { json } from 'body-parser';
      import * as cloneBuffer from 'clone-buffer';
      
      export const cachedRawBodyRequestKey = 'rawBodyBuffer';
      
      /**
       * Clones the request buffer and stores it on the request object for reading later 
       */
      export const cacheRawBodyOnRequest = json({
        verify: (req: any, res, buf, encoding) => {
      
          // only clone the buffer if we're receiving a Xero webhook request
          if (req.headers['x-xero-signature'] && Buffer.isBuffer(buf)) {
            req[cachedRawBodyRequestKey] = cloneBuffer(buf);
          }
          return true;
        },
      });
      

      main.ts:

      app.use(cacheRawBodyOnRequest);
      

      控制器:

      const textBody = req[cachedRawBodyRequestKey].toString('utf-8');
      

      【讨论】:

        【解决方案4】:

        添加@yumaa's post above

        这是 NestJS v7.0.8 的工作装饰器:

        import { createParamDecorator, ExecutionContext, BadRequestException } from '@nestjs/common';
        import * as rawBody from "raw-body";
        
        export const PlainBody = createParamDecorator(async (_, context: ExecutionContext) => {
            const req = context.switchToHttp().getRequest<import("express").Request>();
            if (!req.readable) { throw new BadRequestException("Invalid body"); }
        
            const body = (await rawBody(req)).toString("utf8").trim();
            return body;
        })
        

        【讨论】:

          【解决方案5】:

          我看到这个问题很老了,但它在谷歌中排名第一,所以我想在这里添加答案。

          如果您不想添加 body-parser 中间件(例如,您只希望在单个控制器方法中使用纯文本),您可以使用 raw-body(您的 node_modules 中已经存在),如下所示:

          import * as rawbody from 'raw-body';
          import { Controller, Post, Body, Req } from '@nestjs/common';
          
          @Controller('/')
          export class IndexController {
          
            @Post()
            async index(@Body() data, @Req() req) {
          
              // we have to check req.readable because of raw-body issue #57
              // https://github.com/stream-utils/raw-body/issues/57
              if (req.readable) {
                // body is ignored by NestJS -> get raw body from request
                const raw = await rawbody(req);
                const text = raw.toString().trim();
                console.log('body:', text);
          
              } else {
                // body is parsed by NestJS
                console.log('data:', data);
              }
          
              // ...
            }
          
          }
          

          你也可以创建新的参数装饰器

          import * as rawbody from 'raw-body';
          import { createParamDecorator, HttpException, HttpStatus } from '@nestjs/common';
          
          export const PlainBody = createParamDecorator(async (data, req) => {
            if (req.readable) {
              return (await rawbody(req)).toString().trim();
            }
            throw new HttpException('Body aint text/plain', HttpStatus.INTERNAL_SERVER_ERROR);
          });
          

          并像使用它

          @Post()
          async index(@PlainBody() text: string) {
            // ...
          

          (我没有检查装饰器代码,在评论中写在这里)

          【讨论】:

          • 虽然这行得通(我测试过),但唯一的问题是你不能严格控制类型,因为 rawbody 要求解析到其中的任何内容都是可流式的,而 @Req 实际上不是的 Request 类型那。因此,您必须关闭严格输入才能使其正常工作,例如,如果您还想检查内容长度(您应该这样做)但是可以,所以谢谢!
          • @yumaa 你让我的开发者度过了一个愉快的夜晚! :)
          【解决方案6】:

          Nest 与纯文本不兼容,您必须将 bodyparser 传递给您的 express 应用程序。试试这样的:

          import * as bodyParser from 'body-parser';
          
          
          async function bootstrap() {
            const app = await NestFactory.create(AppModule);
            app.use(bodyparser({ ...options })) // for plain/text bodies
            await app.listen(3000)
          }
          bootstrap();
          

          其中 options 是从 https://www.npmjs.com/package/body-parser

          创建的

          【讨论】:

            【解决方案7】:

            发布请求的语义由指示内容类型的标头确定。尝试确保请求标头的类型为“text/plain”并查看是否有帮助。

            https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST

            【讨论】:

            • 我在 Postman 中尝试过 "text" 和 "text/plain",但它们都没有被 Nest 作为字符串拾取。
            猜你喜欢
            • 2018-11-27
            • 2018-01-03
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-05-31
            • 2013-06-29
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多