Skip to content

Commit

Permalink
Merge branch 'tolgap-express-raw-body'
Browse files Browse the repository at this point in the history
  • Loading branch information
kamilmysliwiec committed May 17, 2022
2 parents 7ca14a2 + 71e3143 commit 79cccd0
Show file tree
Hide file tree
Showing 15 changed files with 219 additions and 6 deletions.
49 changes: 49 additions & 0 deletions integration/nest-application/raw-body/e2e/express.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { NestExpressApplication } from '@nestjs/platform-express';
import { Test } from '@nestjs/testing';
import { expect } from 'chai';
import * as request from 'supertest';
import { ExpressModule } from '../src/express.module';

describe('Raw body (Express Application)', () => {
let app: NestExpressApplication;
const body = '{ "amount":0.0 }';

beforeEach(async () => {
const moduleFixture = await Test.createTestingModule({
imports: [ExpressModule],
}).compile();

app = moduleFixture.createNestApplication<NestExpressApplication>(
undefined,
{ rawBody: true },
);
});

it('should return exact post body', async () => {
await app.init();
const response = await request(app.getHttpServer())
.post('/')
.set('Content-Type', 'application/json')
.send(body)
.expect(201);

expect(response.body).to.eql({
parsed: {
amount: 0,
},
raw: '{ "amount":0.0 }',
});
});

it('should work if post body is empty', async () => {
await app.init();
await request(app.getHttpServer())
.post('/')
.set('Content-Type', 'application/json')
.expect(201);
});

afterEach(async () => {
await app.close();
});
});
49 changes: 49 additions & 0 deletions integration/nest-application/raw-body/e2e/fastify.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { NestFastifyApplication } from '@nestjs/platform-fastify';
import { Test } from '@nestjs/testing';
import { expect } from 'chai';
import * as request from 'supertest';
import { FastifyModule } from '../src/fastify.module';

describe('Raw body (Fastify Application)', () => {
let app: NestFastifyApplication;
const body = '{ "amount":0.0 }';

beforeEach(async () => {
const moduleFixture = await Test.createTestingModule({
imports: [FastifyModule],
}).compile();

app = moduleFixture.createNestApplication<NestFastifyApplication>(null, {
rawBody: true,
});
});

it('should return exact post body', async () => {
await app.init();
const response = await request(app.getHttpServer())
.post('/')
.set('Content-Type', 'application/json')
.set('Accept', 'application/json')
.send(body)
.expect(201);

expect(response.body).to.eql({
parsed: {
amount: 0,
},
raw: '{ "amount":0.0 }',
});
});

it('should work if post body is empty', async () => {
await app.init();
await request(app.getHttpServer())
.post('/')
.set('Content-Type', 'application/json')
.expect(201);
});

afterEach(async () => {
await app.close();
});
});
13 changes: 13 additions & 0 deletions integration/nest-application/raw-body/src/express.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Controller, Post, RawBodyRequest, Req } from '@nestjs/common';
import { Request } from 'express';

@Controller()
export class ExpressController {
@Post()
getRawBody(@Req() req: RawBodyRequest<Request>) {
return {
parsed: req.body,
raw: req.rawBody.toString(),
};
}
}
7 changes: 7 additions & 0 deletions integration/nest-application/raw-body/src/express.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Module } from '@nestjs/common';
import { ExpressController } from './express.controller';

@Module({
controllers: [ExpressController],
})
export class ExpressModule {}
13 changes: 13 additions & 0 deletions integration/nest-application/raw-body/src/fastify.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Controller, Post, RawBodyRequest, Req } from '@nestjs/common';
import type { FastifyRequest } from 'fastify';

@Controller()
export class FastifyController {
@Post()
getRawBody(@Req() req: RawBodyRequest<FastifyRequest>) {
return {
parsed: req.body,
raw: req.rawBody.toString(),
};
}
}
7 changes: 7 additions & 0 deletions integration/nest-application/raw-body/src/fastify.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Module } from '@nestjs/common';
import { FastifyController } from './fastify.controller';

@Module({
controllers: [FastifyController],
})
export class FastifyModule {}
22 changes: 22 additions & 0 deletions integration/nest-application/raw-body/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"compilerOptions": {
"module": "commonjs",
"declaration": false,
"noImplicitAny": false,
"removeComments": true,
"noLib": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es6",
"sourceMap": true,
"allowJs": true,
"outDir": "./dist"
},
"include": [
"src/**/*",
"e2e/**/*"
],
"exclude": [
"node_modules"
]
}
1 change: 1 addition & 0 deletions packages/common/http/interfaces/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './http-module.interface';
export * from './raw-body-request.interface';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type RawBodyRequest<T> = T & { rawBody?: Buffer };
2 changes: 1 addition & 1 deletion packages/common/interfaces/http/http-server.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export interface HttpServer<TRequest = any, TResponse = any> {
getRequestMethod?(request: TRequest): string;
getRequestUrl?(request: TRequest): string;
getInstance(): any;
registerParserMiddleware(): any;
registerParserMiddleware(...args: any[]): any;
enableCors(options: CorsOptions | CorsOptionsDelegate<TRequest>): any;
getHttpServer(): any;
initHttpServer(options: NestApplicationOptions): void;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,8 @@ export interface NestApplicationOptions extends NestApplicationContextOptions {
* Set of configurable HTTPS options
*/
httpsOptions?: HttpsOptions;
/**
* Whether to register the raw request body on the request. Use `req.rawBody`.
*/
rawBody?: boolean;
}
2 changes: 1 addition & 1 deletion packages/core/adapters/http-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export abstract class AbstractHttpAdapter<
abstract setErrorHandler(handler: Function, prefix?: string);
abstract setNotFoundHandler(handler: Function, prefix?: string);
abstract setHeader(response, name: string, value: string);
abstract registerParserMiddleware(prefix?: string);
abstract registerParserMiddleware(prefix?: string, rawBody?: boolean);
abstract enableCors(
options: CorsOptions | CorsOptionsDelegate<TRequest>,
prefix?: string,
Expand Down
4 changes: 3 additions & 1 deletion packages/core/nest-application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,9 @@ export class NestApplication
}

public registerParserMiddleware() {
this.httpAdapter.registerParserMiddleware();
const prefix = this.config.getGlobalPrefix();
const rawBody = !!this.appOptions?.rawBody;
this.httpAdapter.registerParserMiddleware(prefix, rawBody);
}

public async registerRouter() {
Expand Down
18 changes: 16 additions & 2 deletions packages/platform-express/adapters/express-adapter.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
InternalServerErrorException,
RawBodyRequest,
RequestMethod,
StreamableFile,
VersioningType,
Expand All @@ -25,6 +26,7 @@ import { AbstractHttpAdapter } from '@nestjs/core/adapters/http-adapter';
import { RouterMethodFactory } from '@nestjs/core/helpers/router-method-factory';
import {
json as bodyParserJson,
OptionsJson,
urlencoded as bodyParserUrlencoded,
} from 'body-parser';
import * as cors from 'cors';
Expand Down Expand Up @@ -176,9 +178,21 @@ export class ExpressAdapter extends AbstractHttpAdapter {
this.httpServer = http.createServer(this.getInstance());
}

public registerParserMiddleware() {
public registerParserMiddleware(prefix?: string, rawBody?: boolean) {
let bodyParserJsonOptions: OptionsJson | undefined;
if (rawBody === true) {
bodyParserJsonOptions = {
verify: (req: RawBodyRequest<http.IncomingMessage>, _res, buffer) => {
if (Buffer.isBuffer(buffer)) {
req.rawBody = buffer;
}
return true;
},
};
}

const parserMiddleware = {
jsonParser: bodyParserJson(),
jsonParser: bodyParserJson(bodyParserJsonOptions),
urlencodedParser: bodyParserUrlencoded({ extended: true }),
};
Object.keys(parserMiddleware)
Expand Down
33 changes: 32 additions & 1 deletion packages/platform-fastify/adapters/fastify-adapter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
HttpStatus,
Logger,
RawBodyRequest,
RequestMethod,
StreamableFile,
VersioningOptions,
Expand All @@ -16,6 +17,7 @@ import { isString, isUndefined } from '@nestjs/common/utils/shared.utils';
import { AbstractHttpAdapter } from '@nestjs/core/adapters/http-adapter';
import {
fastify,
FastifyBodyParser,
FastifyInstance,
FastifyLoggerInstance,
FastifyPluginAsync,
Expand Down Expand Up @@ -424,11 +426,16 @@ export class FastifyAdapter<
this.register(import('fastify-cors'), options);
}

public registerParserMiddleware() {
public registerParserMiddleware(prefix?: string, rawBody?: boolean) {
if (this._isParserRegistered) {
return;
}
this.register(import('fastify-formbody'));

if (rawBody) {
this.registerContentParserWithRawBody();
}

this._isParserRegistered = true;
}

Expand Down Expand Up @@ -476,6 +483,30 @@ export class FastifyAdapter<
return !('status' in response);
}

private registerContentParserWithRawBody() {
this.getInstance().addContentTypeParser<Buffer>(
'application/json',
{ parseAs: 'buffer' },
(
req: RawBodyRequest<FastifyRequest<unknown, TServer, TRawRequest>>,
body: Buffer,
done,
) => {
if (Buffer.isBuffer(body)) {
req.rawBody = body;
}

const { onProtoPoisoning, onConstructorPoisoning } =
this.instance.initialConfig;
const defaultJsonParser = this.instance.getDefaultJsonParser(
onProtoPoisoning || 'error',
onConstructorPoisoning || 'error',
) as FastifyBodyParser<string | Buffer, TServer>;
defaultJsonParser(req, body, done);
},
);
}

private async registerMiddie() {
this.isMiddieRegistered = true;
await this.register(import('middie'));
Expand Down

0 comments on commit 79cccd0

Please sign in to comment.