如何使用 NestJS 将纯文本作为我的请求正文传递?
How do I pass plain text as my request body using NestJS?
我的 NestJS 应用程序中的一个控制器方法应该将纯文本作为其主体,但每当我尝试发出请求时,参数都会作为空对象接收。这是否可能,或者我是否必须创建某种 DTO 来传递该单个字符串?
示例:
@Post()
myFunction(@Body() id: string) {
// do something here
}
post 请求的语义由指示内容类型的 header 决定。尝试确保请求 header 的类型为 'text/plain',看看这是否有帮助。
https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST
Nest 与 plain/text 不兼容,您必须将 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
创建的
我看到这个问题很老了,但是它在google中列在第一位,所以我想在这里添加答案。
如果您不想添加 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) {
// ...
(我没有检查装饰器代码,直接写在评论里)
添加
这是 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;
})
老问题,但上面的 none 对我有用,但下面的问题:
上述装饰器或 controller-method 方法对我不起作用,因为请求正文缓冲区始终已被读取。
我能够使用以下中间件让它工作。 (请注意,在我的例子中,我需要验证 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');
这是我对在 NestJS 处理程序中获取原始(文本)主体的看法:
- 使用
preserveRawBodyInRequest
配置应用程序,如 JSDoc 示例中所示
- 在处理程序中使用
RawBody
装饰器来检索原始(文本)正文
原始-request.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 方依赖项,您也可以在此处使用 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);
}
我的 NestJS 应用程序中的一个控制器方法应该将纯文本作为其主体,但每当我尝试发出请求时,参数都会作为空对象接收。这是否可能,或者我是否必须创建某种 DTO 来传递该单个字符串?
示例:
@Post()
myFunction(@Body() id: string) {
// do something here
}
post 请求的语义由指示内容类型的 header 决定。尝试确保请求 header 的类型为 'text/plain',看看这是否有帮助。
https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST
Nest 与 plain/text 不兼容,您必须将 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
创建的我看到这个问题很老了,但是它在google中列在第一位,所以我想在这里添加答案。
如果您不想添加 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) {
// ...
(我没有检查装饰器代码,直接写在评论里)
添加
这是 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;
})
老问题,但上面的 none 对我有用,但下面的问题:
上述装饰器或 controller-method 方法对我不起作用,因为请求正文缓冲区始终已被读取。
我能够使用以下中间件让它工作。 (请注意,在我的例子中,我需要验证 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');
这是我对在 NestJS 处理程序中获取原始(文本)主体的看法:
- 使用
preserveRawBodyInRequest
配置应用程序,如 JSDoc 示例中所示 - 在处理程序中使用
RawBody
装饰器来检索原始(文本)正文
原始-request.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 方依赖项,您也可以在此处使用 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);
}