mirror of
https://github.com/nestjs/nest.git
synced 2026-02-23 15:52:50 +00:00
Compare commits
45 Commits
H4ad-perf/
...
e-dot-mast
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d616861698 | ||
|
|
c64312fded | ||
|
|
304e6bf5bb | ||
|
|
161198379a | ||
|
|
661104d981 | ||
|
|
f71554ca96 | ||
|
|
a986e7e907 | ||
|
|
920f430d4d | ||
|
|
cd0462fbcb | ||
|
|
b9910a400a | ||
|
|
6d88e3cd3c | ||
|
|
fbf1ab7a81 | ||
|
|
868bbcd8b9 | ||
|
|
4e783852b3 | ||
|
|
07a84ae224 | ||
|
|
7bc72671b6 | ||
|
|
a560c3466a | ||
|
|
f10c917b71 | ||
|
|
208d8ca184 | ||
|
|
32aeb7a8a3 | ||
|
|
a185e63281 | ||
|
|
f396dc1ddf | ||
|
|
aafdac62a0 | ||
|
|
e8944675cb | ||
|
|
4d4f3f82de | ||
|
|
994f8c4e42 | ||
|
|
58ee3bf2f3 | ||
|
|
5deabef549 | ||
|
|
9a47a6ce70 | ||
|
|
eee8968d82 | ||
|
|
35357053b1 | ||
|
|
ceea86d6ed | ||
|
|
363ab79b5f | ||
|
|
9efb84fc22 | ||
|
|
1b90339d8b | ||
|
|
a25bfa86ed | ||
|
|
d5e1cd18fc | ||
|
|
9b073748cb | ||
|
|
0444f9c89e | ||
|
|
1b9bb76d07 | ||
|
|
c817014c7d | ||
|
|
accb1dc014 | ||
|
|
1755537a77 | ||
|
|
7b81d0571d | ||
|
|
7afed825be |
@@ -0,0 +1,95 @@
|
||||
import { NestExpressApplication } from '@nestjs/platform-express';
|
||||
import { Test } from '@nestjs/testing';
|
||||
import { OptionsUrlencoded } from 'body-parser';
|
||||
import { expect } from 'chai';
|
||||
import * as request from 'supertest';
|
||||
import { AppModule } from '../src/app.module';
|
||||
|
||||
describe('Body Parser (Express Application)', () => {
|
||||
const moduleFixture = Test.createTestingModule({
|
||||
imports: [AppModule],
|
||||
});
|
||||
let app: NestExpressApplication;
|
||||
|
||||
afterEach(async () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
describe('application/json', () => {
|
||||
const stringLimit = '{ "msg": "Hello, World" }';
|
||||
const stringOverLimit = '{ "msg": "Hello, World!" }';
|
||||
|
||||
beforeEach(async () => {
|
||||
const testFixture = await moduleFixture.compile();
|
||||
|
||||
app = testFixture
|
||||
.createNestApplication<NestExpressApplication>({
|
||||
rawBody: true,
|
||||
logger: false,
|
||||
})
|
||||
.useBodyParser('json', { limit: Buffer.from(stringLimit).byteLength });
|
||||
|
||||
await app.init();
|
||||
});
|
||||
|
||||
it('should allow request with matching body limit', async () => {
|
||||
const response = await request(app.getHttpServer())
|
||||
.post('/')
|
||||
.set('Content-Type', 'application/json')
|
||||
.send(stringLimit)
|
||||
.expect(201);
|
||||
|
||||
expect(response.body).to.eql({
|
||||
raw: stringLimit,
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail if post body is larger than limit', async () => {
|
||||
await request(app.getHttpServer())
|
||||
.post('/')
|
||||
.set('Content-Type', 'application/json')
|
||||
.send(stringOverLimit)
|
||||
.expect(413);
|
||||
});
|
||||
});
|
||||
|
||||
describe('application/x-www-form-urlencoded', () => {
|
||||
const stringLimit = 'msg=Hello, World';
|
||||
const stringOverLimit = 'msg=Hello, World!';
|
||||
|
||||
beforeEach(async () => {
|
||||
const testFixture = await moduleFixture.compile();
|
||||
|
||||
app = testFixture
|
||||
.createNestApplication<NestExpressApplication>({
|
||||
rawBody: true,
|
||||
logger: false,
|
||||
})
|
||||
.useBodyParser<OptionsUrlencoded>('urlencoded', {
|
||||
limit: Buffer.from(stringLimit).byteLength,
|
||||
extended: true,
|
||||
});
|
||||
|
||||
await app.init();
|
||||
});
|
||||
it('should allow request with matching body limit', async () => {
|
||||
const response = await request(app.getHttpServer())
|
||||
.post('/')
|
||||
.set('Content-Type', 'application/x-www-form-urlencoded')
|
||||
.send(stringLimit)
|
||||
.expect(201);
|
||||
|
||||
expect(response.body).to.eql({
|
||||
raw: stringLimit,
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail if post body is larger than limit', async () => {
|
||||
await request(app.getHttpServer())
|
||||
.post('/')
|
||||
.set('Content-Type', 'application/x-www-form-urlencoded')
|
||||
.send(stringOverLimit)
|
||||
.expect(413);
|
||||
});
|
||||
});
|
||||
});
|
||||
106
integration/nest-application/use-body-parser/e2e/fastify.spec.ts
Normal file
106
integration/nest-application/use-body-parser/e2e/fastify.spec.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import {
|
||||
FastifyAdapter,
|
||||
NestFastifyApplication,
|
||||
} from '@nestjs/platform-fastify';
|
||||
import { Test } from '@nestjs/testing';
|
||||
import { expect } from 'chai';
|
||||
import { AppModule } from '../src/app.module';
|
||||
|
||||
describe('Body Parser (Fastify Application)', () => {
|
||||
const moduleFixture = Test.createTestingModule({
|
||||
imports: [AppModule],
|
||||
});
|
||||
let app: NestFastifyApplication;
|
||||
|
||||
afterEach(async () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
describe('application/json', () => {
|
||||
const stringLimit = '{ "msg": "Hello, World" }';
|
||||
const stringOverLimit = '{ "msg": "Hello, World!" }';
|
||||
|
||||
beforeEach(async () => {
|
||||
const testFixture = await moduleFixture.compile();
|
||||
|
||||
app = testFixture
|
||||
.createNestApplication<NestFastifyApplication>(new FastifyAdapter(), {
|
||||
rawBody: true,
|
||||
logger: false,
|
||||
})
|
||||
.useBodyParser('application/json', {
|
||||
bodyLimit: Buffer.from(stringLimit).byteLength,
|
||||
});
|
||||
|
||||
await app.init();
|
||||
});
|
||||
|
||||
it('should allow request with matching body limit', async () => {
|
||||
const response = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
payload: stringLimit,
|
||||
});
|
||||
|
||||
expect(JSON.parse(response.body)).to.eql({
|
||||
raw: stringLimit,
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail if post body is larger than limit', async () => {
|
||||
const response = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
payload: stringOverLimit,
|
||||
});
|
||||
|
||||
expect(response.statusCode).to.equal(413);
|
||||
});
|
||||
});
|
||||
|
||||
describe('application/x-www-form-urlencoded', () => {
|
||||
const stringLimit = 'msg=Hello, World';
|
||||
const stringOverLimit = 'msg=Hello, World!';
|
||||
|
||||
beforeEach(async () => {
|
||||
const testFixture = await moduleFixture.compile();
|
||||
|
||||
app = testFixture
|
||||
.createNestApplication<NestFastifyApplication>(new FastifyAdapter(), {
|
||||
rawBody: true,
|
||||
logger: false,
|
||||
})
|
||||
.useBodyParser('application/x-www-form-urlencoded', {
|
||||
bodyLimit: Buffer.from(stringLimit).byteLength,
|
||||
});
|
||||
|
||||
await app.init();
|
||||
});
|
||||
|
||||
it('should allow request with matching body limit', async () => {
|
||||
const response = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/',
|
||||
headers: { 'content-type': 'application/x-www-form-urlencoded' },
|
||||
payload: stringLimit,
|
||||
});
|
||||
|
||||
expect(JSON.parse(response.body)).to.eql({
|
||||
raw: stringLimit,
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail if post body is larger than limit', async () => {
|
||||
const response = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/',
|
||||
headers: { 'content-type': 'application/x-www-form-urlencoded' },
|
||||
payload: stringOverLimit,
|
||||
});
|
||||
|
||||
expect(response.statusCode).to.equal(413);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Controller, Post, Req, RawBodyRequest } from '@nestjs/common';
|
||||
import { IncomingMessage } from 'http';
|
||||
|
||||
@Controller()
|
||||
export class AppController {
|
||||
@Post()
|
||||
index(@Req() req: RawBodyRequest<IncomingMessage>) {
|
||||
return {
|
||||
raw: req.rawBody?.toString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { AppController } from './app.controller';
|
||||
|
||||
@Module({
|
||||
controllers: [AppController],
|
||||
})
|
||||
export class AppModule {}
|
||||
23
integration/nest-application/use-body-parser/tsconfig.json
Normal file
23
integration/nest-application/use-body-parser/tsconfig.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"declaration": false,
|
||||
"noImplicitAny": false,
|
||||
"removeComments": true,
|
||||
"lib": ["dom"],
|
||||
"noLib": false,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"target": "es6",
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"outDir": "./dist"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"e2e/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
@@ -30,6 +30,7 @@ export interface HttpServer<TRequest = any, TResponse = any> {
|
||||
| RequestHandler<TRequest, TResponse>
|
||||
| ErrorHandler<TRequest, TResponse>,
|
||||
): any;
|
||||
useBodyParser?(...args: any[]): any;
|
||||
get(handler: RequestHandler<TRequest, TResponse>): any;
|
||||
get(path: string, handler: RequestHandler<TRequest, TResponse>): any;
|
||||
post(handler: RequestHandler<TRequest, TResponse>): any;
|
||||
|
||||
@@ -205,7 +205,7 @@ export class ConfigurableModuleBuilder<
|
||||
static [self.staticMethodKey](
|
||||
options: ModuleOptions & ExtraModuleDefinitionOptions,
|
||||
): DynamicModule {
|
||||
const providers = [
|
||||
const providers: Array<Provider> = [
|
||||
{
|
||||
provide: self.options.optionsInjectionToken,
|
||||
useValue: this.omitExtras(options, self.extras),
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Injectable } from '../decorators/core/injectable.decorator';
|
||||
import { Optional } from '../decorators/core/optional.decorator';
|
||||
import { Injectable, Optional } from '../decorators/core';
|
||||
import { isObject } from '../utils/shared.utils';
|
||||
import { ConsoleLogger } from './console-logger.service';
|
||||
import { isLogLevelEnabled } from './utils';
|
||||
|
||||
@@ -9,16 +9,16 @@ import { isEmpty } from '@nestjs/common/utils/shared.utils';
|
||||
import { lastValueFrom, isObservable } from 'rxjs';
|
||||
import { ExternalExceptionFilterContext } from '../exceptions/external-exception-filter-context';
|
||||
import { FORBIDDEN_MESSAGE } from '../guards/constants';
|
||||
import { GuardsConsumer } from '../guards/guards-consumer';
|
||||
import { GuardsContextCreator } from '../guards/guards-context-creator';
|
||||
import { GuardsConsumer, GuardsContextCreator } from '../guards';
|
||||
import { STATIC_CONTEXT } from '../injector/constants';
|
||||
import { NestContainer } from '../injector/container';
|
||||
import { ContextId } from '../injector/instance-wrapper';
|
||||
import { ModulesContainer } from '../injector/modules-container';
|
||||
import { InterceptorsConsumer } from '../interceptors/interceptors-consumer';
|
||||
import { InterceptorsContextCreator } from '../interceptors/interceptors-context-creator';
|
||||
import { PipesConsumer } from '../pipes/pipes-consumer';
|
||||
import { PipesContextCreator } from '../pipes/pipes-context-creator';
|
||||
import {
|
||||
InterceptorsConsumer,
|
||||
InterceptorsContextCreator,
|
||||
} from '../interceptors';
|
||||
import { PipesConsumer, PipesContextCreator } from '../pipes';
|
||||
import { ContextUtils, ParamProperties } from './context-utils';
|
||||
import { ExternalErrorProxy } from './external-proxy';
|
||||
import { HandlerMetadataStorage } from './handler-metadata-storage';
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
isUndefined,
|
||||
} from '@nestjs/common/utils/shared.utils';
|
||||
import { iterate } from 'iterare';
|
||||
import { DeterministicUuidRegistry } from '../inspector/deterministic-uuid-registry';
|
||||
import { UuidFactory } from '../inspector/uuid-factory';
|
||||
import { STATIC_CONTEXT } from './constants';
|
||||
import {
|
||||
isClassProvider,
|
||||
@@ -469,6 +469,6 @@ export class InstanceWrapper<T = any> {
|
||||
let key = this.name?.toString() ?? this.token?.toString();
|
||||
key += this.host?.name ?? '';
|
||||
|
||||
return key ? DeterministicUuidRegistry.get(key) : randomStringGenerator();
|
||||
return key ? UuidFactory.get(key) : randomStringGenerator();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,11 +30,11 @@ import {
|
||||
import { createContextId } from '../helpers/context-id-factory';
|
||||
import { getClassScope } from '../helpers/get-class-scope';
|
||||
import { isDurable } from '../helpers/is-durable';
|
||||
import { DeterministicUuidRegistry } from '../inspector/deterministic-uuid-registry';
|
||||
import { UuidFactory } from '../inspector/uuid-factory';
|
||||
import { CONTROLLER_ID_KEY } from './constants';
|
||||
import { NestContainer } from './container';
|
||||
import { InstanceWrapper } from './instance-wrapper';
|
||||
import { ModuleRefGetOrResolveOpts, ModuleRef } from './module-ref';
|
||||
import { ModuleRef, ModuleRefGetOrResolveOpts } from './module-ref';
|
||||
|
||||
/**
|
||||
* @note
|
||||
@@ -626,8 +626,6 @@ export class Module {
|
||||
private generateUuid(): string {
|
||||
const UUID_NAMESPACE = 'fb848993-0c82-4b9e-ae95-3c3c1dbe3d6b';
|
||||
const key = this.name?.toString() ?? this.token?.toString();
|
||||
return key
|
||||
? DeterministicUuidRegistry.get(key, UUID_NAMESPACE)
|
||||
: randomStringGenerator();
|
||||
return key ? UuidFactory.get(key, UUID_NAMESPACE) : randomStringGenerator();
|
||||
}
|
||||
}
|
||||
|
||||
21
packages/core/inspector/uuid-factory.ts
Normal file
21
packages/core/inspector/uuid-factory.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { randomStringGenerator } from '../../common/utils/random-string-generator.util';
|
||||
import { DeterministicUuidRegistry } from './deterministic-uuid-registry';
|
||||
|
||||
export enum UuidFactoryMode {
|
||||
Random = 'random',
|
||||
Deterministic = 'deterministic',
|
||||
}
|
||||
|
||||
export class UuidFactory {
|
||||
private static _mode: UuidFactoryMode = UuidFactoryMode.Random;
|
||||
|
||||
static set mode(value: UuidFactoryMode) {
|
||||
this._mode = value;
|
||||
}
|
||||
|
||||
static get(key: string = '', namespace?: string) {
|
||||
return this._mode === UuidFactoryMode.Deterministic
|
||||
? DeterministicUuidRegistry.get(key, namespace)
|
||||
: randomStringGenerator();
|
||||
}
|
||||
}
|
||||
@@ -4,36 +4,84 @@ import {
|
||||
isFunction,
|
||||
isNil,
|
||||
} from '@nestjs/common/utils/shared.utils';
|
||||
import { iterate } from 'iterare';
|
||||
|
||||
export class MetadataScanner {
|
||||
public scanFromPrototype<T extends Injectable, R = any>(
|
||||
instance: T,
|
||||
prototype: object,
|
||||
prototype: object | null,
|
||||
callback: (name: string) => R,
|
||||
): R[] {
|
||||
const methodNames = new Set(this.getAllFilteredMethodNames(prototype));
|
||||
return iterate(methodNames)
|
||||
.map(callback)
|
||||
.filter(metadata => !isNil(metadata))
|
||||
.toArray();
|
||||
}
|
||||
if (!prototype) {
|
||||
return [];
|
||||
}
|
||||
const visitedNames = new Map<string, boolean>();
|
||||
const result: R[] = [];
|
||||
|
||||
*getAllFilteredMethodNames(prototype: object): IterableIterator<string> {
|
||||
const isMethod = (prop: string) => {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(prototype, prop);
|
||||
if (descriptor.set || descriptor.get) {
|
||||
return false;
|
||||
}
|
||||
return !isConstructor(prop) && isFunction(prototype[prop]);
|
||||
};
|
||||
do {
|
||||
yield* iterate(Object.getOwnPropertyNames(prototype))
|
||||
.filter(isMethod)
|
||||
.toArray();
|
||||
for (const property of Object.getOwnPropertyNames(prototype)) {
|
||||
if (visitedNames.has(property)) {
|
||||
continue;
|
||||
}
|
||||
visitedNames.set(property, true);
|
||||
|
||||
const descriptor = Object.getOwnPropertyDescriptor(prototype, property);
|
||||
if (
|
||||
descriptor.set ||
|
||||
descriptor.get ||
|
||||
isConstructor(property) ||
|
||||
!isFunction(prototype[property])
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const value = callback(property);
|
||||
if (isNil(value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
result.push(value);
|
||||
}
|
||||
} while (
|
||||
(prototype = Reflect.getPrototypeOf(prototype)) &&
|
||||
prototype !== Object.prototype
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
*getAllFilteredMethodNames(
|
||||
prototype: object | null,
|
||||
): IterableIterator<string> {
|
||||
if (!prototype) {
|
||||
return [];
|
||||
}
|
||||
const visitedNames = new Map<string, boolean>();
|
||||
const result: string[] = [];
|
||||
|
||||
do {
|
||||
for (const property of Object.getOwnPropertyNames(prototype)) {
|
||||
if (visitedNames.has(property)) {
|
||||
continue;
|
||||
}
|
||||
visitedNames.set(property, true);
|
||||
|
||||
const descriptor = Object.getOwnPropertyDescriptor(prototype, property);
|
||||
if (
|
||||
descriptor.set ||
|
||||
descriptor.get ||
|
||||
isConstructor(property) ||
|
||||
!isFunction(prototype[property])
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
result.push(property);
|
||||
}
|
||||
} while (
|
||||
(prototype = Reflect.getPrototypeOf(prototype)) &&
|
||||
prototype !== Object.prototype
|
||||
);
|
||||
|
||||
return result.values();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import { ExecutionContextHost } from '../helpers/execution-context-host';
|
||||
import { STATIC_CONTEXT } from '../injector/constants';
|
||||
import { NestContainer } from '../injector/container';
|
||||
import { Injector } from '../injector/injector';
|
||||
import { InstanceWrapper } from '../injector/instance-wrapper';
|
||||
import { ContextId, InstanceWrapper } from '../injector/instance-wrapper';
|
||||
import { InstanceToken, Module } from '../injector/module';
|
||||
import { GraphInspector } from '../inspector/graph-inspector';
|
||||
import {
|
||||
@@ -250,6 +250,9 @@ export class MiddlewareModule<
|
||||
const proxy = await this.createProxy(instance);
|
||||
return this.registerHandler(applicationRef, routeInfo, proxy);
|
||||
}
|
||||
|
||||
const isTreeDurable = wrapper.isDependencyTreeDurable();
|
||||
|
||||
await this.registerHandler(
|
||||
applicationRef,
|
||||
routeInfo,
|
||||
@@ -259,19 +262,7 @@ export class MiddlewareModule<
|
||||
next: () => void,
|
||||
) => {
|
||||
try {
|
||||
const contextId = ContextIdFactory.getByRequest(req);
|
||||
if (!req[REQUEST_CONTEXT_ID]) {
|
||||
Object.defineProperty(req, REQUEST_CONTEXT_ID, {
|
||||
value: contextId,
|
||||
enumerable: false,
|
||||
writable: false,
|
||||
configurable: false,
|
||||
});
|
||||
this.container.registerRequestProvider(
|
||||
contextId.getParent ? contextId.payload : req,
|
||||
contextId,
|
||||
);
|
||||
}
|
||||
const contextId = this.getContextId(req, isTreeDurable);
|
||||
const contextInstance = await this.injector.loadPerContext(
|
||||
instance,
|
||||
moduleRef,
|
||||
@@ -325,10 +316,10 @@ export class MiddlewareModule<
|
||||
) {
|
||||
const prefix = this.config.getGlobalPrefix();
|
||||
const excludedRoutes = this.config.getGlobalPrefixOptions().exclude;
|
||||
|
||||
if (
|
||||
(Array.isArray(excludedRoutes) &&
|
||||
isRouteExcluded(excludedRoutes, path, method)) ||
|
||||
['*', '/*', '(.*)', '/(.*)'].includes(path)
|
||||
Array.isArray(excludedRoutes) &&
|
||||
isRouteExcluded(excludedRoutes, path, method)
|
||||
) {
|
||||
path = addLeadingSlash(path);
|
||||
} else {
|
||||
@@ -352,20 +343,40 @@ export class MiddlewareModule<
|
||||
const isMethodAll = isRequestMethodAll(method);
|
||||
const requestMethod = RequestMethod[method];
|
||||
const router = await applicationRef.createMiddlewareFactory(method);
|
||||
router(
|
||||
path,
|
||||
isMethodAll
|
||||
? proxy
|
||||
: <TRequest, TResponse>(
|
||||
req: TRequest,
|
||||
res: TResponse,
|
||||
next: () => void,
|
||||
) => {
|
||||
if (applicationRef.getRequestMethod(req) === requestMethod) {
|
||||
return proxy(req, res, next);
|
||||
}
|
||||
return next();
|
||||
},
|
||||
);
|
||||
const middlewareFunction = isMethodAll
|
||||
? proxy
|
||||
: <TRequest, TResponse>(
|
||||
req: TRequest,
|
||||
res: TResponse,
|
||||
next: () => void,
|
||||
) => {
|
||||
if (applicationRef.getRequestMethod(req) === requestMethod) {
|
||||
return proxy(req, res, next);
|
||||
}
|
||||
return next();
|
||||
};
|
||||
|
||||
const isAWildcard = ['*', '/*', '(.*)', '/(.*)'].includes(path);
|
||||
if (isAWildcard && Array.isArray(excludedRoutes)) {
|
||||
excludedRoutes.forEach(route => router(route.path, middlewareFunction));
|
||||
}
|
||||
|
||||
router(path, middlewareFunction);
|
||||
}
|
||||
|
||||
private getContextId(request: unknown, isTreeDurable: boolean): ContextId {
|
||||
const contextId = ContextIdFactory.getByRequest(request);
|
||||
if (!request[REQUEST_CONTEXT_ID]) {
|
||||
Object.defineProperty(request, REQUEST_CONTEXT_ID, {
|
||||
value: contextId,
|
||||
enumerable: false,
|
||||
writable: false,
|
||||
configurable: false,
|
||||
});
|
||||
|
||||
const requestProviderValue = isTreeDurable ? contextId.payload : request;
|
||||
this.container.registerRequestProvider(requestProviderValue, contextId);
|
||||
}
|
||||
return contextId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ export const mapToExcludeRoute = (
|
||||
return routes.map(({ path, method }) => ({
|
||||
pathRegex: pathToRegexp(path),
|
||||
requestMethod: method,
|
||||
path,
|
||||
}));
|
||||
};
|
||||
|
||||
|
||||
@@ -323,13 +323,20 @@ export class NestApplicationContext<
|
||||
* @param {string[]} signals The system signals it should listen to
|
||||
*/
|
||||
protected listenToShutdownSignals(signals: string[]) {
|
||||
let receivedSignal = false;
|
||||
const cleanup = async (signal: string) => {
|
||||
try {
|
||||
signals.forEach(sig => process.removeListener(sig, cleanup));
|
||||
if (receivedSignal) {
|
||||
// If we receive another signal while we're waiting
|
||||
// for the server to stop, just ignore it.
|
||||
return;
|
||||
}
|
||||
receivedSignal = true;
|
||||
await this.callDestroyHook();
|
||||
await this.callBeforeShutdownHook(signal);
|
||||
await this.dispose();
|
||||
await this.callShutdownHook(signal);
|
||||
signals.forEach(sig => process.removeListener(sig, cleanup));
|
||||
process.kill(process.pid, signal);
|
||||
} catch (err) {
|
||||
Logger.error(
|
||||
|
||||
@@ -266,6 +266,20 @@ export class NestApplication
|
||||
return this;
|
||||
}
|
||||
|
||||
public useBodyParser(...args: [any, any?]): this {
|
||||
if (!('useBodyParser' in this.httpAdapter)) {
|
||||
this.logger.warn('Your HTTP Adapter does not support `.useBodyParser`.');
|
||||
return this;
|
||||
}
|
||||
|
||||
const [parserType, ...otherArgs] = args;
|
||||
const rawBody = !!this.appOptions.rawBody;
|
||||
|
||||
this.httpAdapter.useBodyParser(...[parserType, rawBody, ...otherArgs]);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public enableCors(options?: CorsOptions | CorsOptionsDelegate<any>): void {
|
||||
this.httpAdapter.enableCors(options);
|
||||
}
|
||||
@@ -364,11 +378,13 @@ export class NestApplication
|
||||
return {
|
||||
requestMethod: RequestMethod.ALL,
|
||||
pathRegex: pathToRegexp(addLeadingSlash(route)),
|
||||
path: route,
|
||||
};
|
||||
}
|
||||
return {
|
||||
requestMethod: route.method,
|
||||
pathRegex: pathToRegexp(addLeadingSlash(route.path)),
|
||||
path: route.path,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
@@ -21,6 +21,7 @@ import { Injector } from './injector/injector';
|
||||
import { InstanceLoader } from './injector/instance-loader';
|
||||
import { GraphInspector } from './inspector/graph-inspector';
|
||||
import { NoopGraphInspector } from './inspector/noop-graph-inspector';
|
||||
import { UuidFactory, UuidFactoryMode } from './inspector/uuid-factory';
|
||||
import { MetadataScanner } from './metadata-scanner';
|
||||
import { NestApplication } from './nest-application';
|
||||
import { NestApplicationContext } from './nest-application-context';
|
||||
@@ -195,6 +196,10 @@ export class NestFactoryStatic {
|
||||
options: NestApplicationContextOptions = {},
|
||||
httpServer: HttpServer = null,
|
||||
) {
|
||||
UuidFactory.mode = options.snapshot
|
||||
? UuidFactoryMode.Deterministic
|
||||
: UuidFactoryMode.Random;
|
||||
|
||||
const injector = new Injector({ preview: options.preview });
|
||||
const instanceLoader = new InstanceLoader(
|
||||
container,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { RequestMethod } from '@nestjs/common';
|
||||
|
||||
export interface ExcludeRouteMetadata {
|
||||
path: string;
|
||||
/**
|
||||
* Regular expression representing the route path.
|
||||
*/
|
||||
|
||||
@@ -13,8 +13,7 @@ import {
|
||||
import * as pathToRegexp from 'path-to-regexp';
|
||||
import { ApplicationConfig } from '../application-config';
|
||||
import { UnknownRequestMappingException } from '../errors/exceptions/unknown-request-mapping.exception';
|
||||
import { GuardsConsumer } from '../guards/guards-consumer';
|
||||
import { GuardsContextCreator } from '../guards/guards-context-creator';
|
||||
import { GuardsConsumer, GuardsContextCreator } from '../guards';
|
||||
import { ContextIdFactory } from '../helpers/context-id-factory';
|
||||
import { ExecutionContextHost } from '../helpers/execution-context-host';
|
||||
import {
|
||||
@@ -27,16 +26,17 @@ import { NestContainer } from '../injector/container';
|
||||
import { Injector } from '../injector/injector';
|
||||
import { ContextId, InstanceWrapper } from '../injector/instance-wrapper';
|
||||
import { Module } from '../injector/module';
|
||||
import {
|
||||
InterceptorsConsumer,
|
||||
InterceptorsContextCreator,
|
||||
} from '../interceptors';
|
||||
import { GraphInspector } from '../inspector/graph-inspector';
|
||||
import {
|
||||
Entrypoint,
|
||||
HttpEntrypointMetadata,
|
||||
} from '../inspector/interfaces/entrypoint.interface';
|
||||
import { InterceptorsConsumer } from '../interceptors/interceptors-consumer';
|
||||
import { InterceptorsContextCreator } from '../interceptors/interceptors-context-creator';
|
||||
import { MetadataScanner } from '../metadata-scanner';
|
||||
import { PipesConsumer } from '../pipes/pipes-consumer';
|
||||
import { PipesContextCreator } from '../pipes/pipes-context-creator';
|
||||
import { PipesConsumer, PipesContextCreator } from '../pipes';
|
||||
import { ExceptionsFilter } from './interfaces/exceptions-filter.interface';
|
||||
import { RoutePathMetadata } from './interfaces/route-path-metadata.interface';
|
||||
import { PathsExplorer } from './paths-explorer';
|
||||
@@ -355,13 +355,16 @@ export class RouterExplorer {
|
||||
) {
|
||||
const { instance } = instanceWrapper;
|
||||
const collection = moduleRef.controllers;
|
||||
|
||||
const isTreeDurable = instanceWrapper.isDependencyTreeDurable();
|
||||
|
||||
return async <TRequest extends Record<any, any>, TResponse>(
|
||||
req: TRequest,
|
||||
res: TResponse,
|
||||
next: () => void,
|
||||
) => {
|
||||
try {
|
||||
const contextId = this.getContextId(req);
|
||||
const contextId = this.getContextId(req, isTreeDurable);
|
||||
const contextInstance = await this.injector.loadPerContext(
|
||||
instance,
|
||||
moduleRef,
|
||||
@@ -397,6 +400,7 @@ export class RouterExplorer {
|
||||
|
||||
private getContextId<T extends Record<any, unknown> = any>(
|
||||
request: T,
|
||||
isTreeDurable: boolean,
|
||||
): ContextId {
|
||||
const contextId = ContextIdFactory.getByRequest(request);
|
||||
if (!request[REQUEST_CONTEXT_ID as any]) {
|
||||
@@ -406,10 +410,9 @@ export class RouterExplorer {
|
||||
writable: false,
|
||||
configurable: false,
|
||||
});
|
||||
this.container.registerRequestProvider(
|
||||
contextId.getParent ? contextId.payload : request,
|
||||
contextId,
|
||||
);
|
||||
|
||||
const requestProviderValue = isTreeDurable ? contextId.payload : request;
|
||||
this.container.registerRequestProvider(requestProviderValue, contextId);
|
||||
}
|
||||
return contextId;
|
||||
}
|
||||
|
||||
@@ -23,10 +23,11 @@ import {
|
||||
PipeTransform,
|
||||
Scope,
|
||||
ValueProvider,
|
||||
Controller,
|
||||
Injectable,
|
||||
Type,
|
||||
} from '@nestjs/common/interfaces';
|
||||
import { Controller } from '@nestjs/common/interfaces/controllers/controller.interface';
|
||||
import { Injectable } from '@nestjs/common/interfaces/injectable.interface';
|
||||
import { Type } from '@nestjs/common/interfaces/type.interface';
|
||||
import { randomStringGenerator } from '@nestjs/common/utils/random-string-generator.util';
|
||||
import {
|
||||
isFunction,
|
||||
isNil,
|
||||
@@ -50,8 +51,8 @@ import { NestContainer } from './injector/container';
|
||||
import { InstanceWrapper } from './injector/instance-wrapper';
|
||||
import { InternalCoreModuleFactory } from './injector/internal-core-module/internal-core-module-factory';
|
||||
import { Module } from './injector/module';
|
||||
import { DeterministicUuidRegistry } from './inspector/deterministic-uuid-registry';
|
||||
import { GraphInspector } from './inspector/graph-inspector';
|
||||
import { UuidFactory } from './inspector/uuid-factory';
|
||||
import { MetadataScanner } from './metadata-scanner';
|
||||
|
||||
interface ApplicationProviderWrapper {
|
||||
@@ -410,7 +411,7 @@ export class DependenciesScanner {
|
||||
if (!providersKeys.includes(type as string)) {
|
||||
return this.container.addProvider(provider as any, token);
|
||||
}
|
||||
const uuid = DeterministicUuidRegistry.get(type.toString());
|
||||
const uuid = UuidFactory.get(type.toString());
|
||||
const providerToken = `${type as string} (UUID: ${uuid})`;
|
||||
|
||||
let scope = (provider as ClassProvider | FactoryProvider).scope;
|
||||
|
||||
@@ -23,7 +23,11 @@ describe('ApplicationConfig', () => {
|
||||
it('should set global path options', () => {
|
||||
const options: GlobalPrefixOptions<ExcludeRouteMetadata> = {
|
||||
exclude: [
|
||||
{ pathRegex: new RegExp(/health/), requestMethod: RequestMethod.GET },
|
||||
{
|
||||
pathRegex: new RegExp(/health/),
|
||||
requestMethod: RequestMethod.GET,
|
||||
path: 'health',
|
||||
},
|
||||
],
|
||||
};
|
||||
appConfig.setGlobalPrefixOptions(options);
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Injector } from '../injector/injector';
|
||||
import { InstanceLoader } from '../injector/instance-loader';
|
||||
import { GraphInspector } from '../inspector/graph-inspector';
|
||||
import { NestApplicationContext } from '../nest-application-context';
|
||||
import * as sinon from 'sinon';
|
||||
|
||||
describe('NestApplicationContext', () => {
|
||||
class A {}
|
||||
@@ -49,6 +50,54 @@ describe('NestApplicationContext', () => {
|
||||
return applicationContext;
|
||||
}
|
||||
|
||||
describe('listenToShutdownSignals', () => {
|
||||
it('shutdown process should not be interrupted by another handler', async () => {
|
||||
const signal = 'SIGTERM';
|
||||
let processUp = true;
|
||||
let promisesResolved = false;
|
||||
const applicationContext = await testHelper(A, Scope.DEFAULT);
|
||||
applicationContext.enableShutdownHooks([signal]);
|
||||
|
||||
const waitProcessDown = new Promise(resolve => {
|
||||
const shutdownCleanupRef = applicationContext['shutdownCleanupRef'];
|
||||
const handler = () => {
|
||||
if (
|
||||
!process
|
||||
.listeners(signal)
|
||||
.find(handler => handler == shutdownCleanupRef)
|
||||
) {
|
||||
processUp = false;
|
||||
process.removeListener(signal, handler);
|
||||
resolve(undefined);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
process.on(signal, handler);
|
||||
});
|
||||
|
||||
// add some third party handler
|
||||
process.on(signal, signal => {
|
||||
// do some work
|
||||
process.kill(process.pid, signal);
|
||||
});
|
||||
|
||||
const hookStub = sinon
|
||||
.stub(applicationContext as any, 'callShutdownHook')
|
||||
.callsFake(async () => {
|
||||
// run some async code
|
||||
await new Promise(resolve => setImmediate(() => resolve(undefined)));
|
||||
if (processUp) {
|
||||
promisesResolved = true;
|
||||
}
|
||||
});
|
||||
process.kill(process.pid, signal);
|
||||
await waitProcessDown;
|
||||
hookStub.restore();
|
||||
expect(processUp).to.be.false;
|
||||
expect(promisesResolved).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe('get', () => {
|
||||
describe('when scope = DEFAULT', () => {
|
||||
it('should get value with function injection key', async () => {
|
||||
|
||||
@@ -249,6 +249,7 @@ describe('RoutePathFactory', () => {
|
||||
{
|
||||
pathRegex: pathToRegexp('/random'),
|
||||
requestMethod: RequestMethod.ALL,
|
||||
path: '/random',
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -267,6 +268,7 @@ describe('RoutePathFactory', () => {
|
||||
{
|
||||
pathRegex: pathToRegexp('/cats'),
|
||||
requestMethod: RequestMethod.ALL,
|
||||
path: '/cats',
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -285,6 +287,7 @@ describe('RoutePathFactory', () => {
|
||||
{
|
||||
pathRegex: pathToRegexp('/cats'),
|
||||
requestMethod: RequestMethod.GET,
|
||||
path: '/cats',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
@@ -205,13 +205,15 @@ export class ListenersController {
|
||||
const collection = moduleRef.controllers;
|
||||
const { instance } = wrapper;
|
||||
|
||||
const isTreeDurable = wrapper.isDependencyTreeDurable();
|
||||
|
||||
const requestScopedHandler: MessageHandler = async (...args: unknown[]) => {
|
||||
try {
|
||||
let contextId: ContextId;
|
||||
|
||||
let [dataOrContextHost] = args;
|
||||
if (dataOrContextHost instanceof RequestContextHost) {
|
||||
contextId = this.getContextId(dataOrContextHost);
|
||||
contextId = this.getContextId(dataOrContextHost, isTreeDurable);
|
||||
args.shift();
|
||||
} else {
|
||||
const [data, reqCtx] = args;
|
||||
@@ -220,11 +222,7 @@ export class ListenersController {
|
||||
data,
|
||||
reqCtx as BaseRpcContext,
|
||||
);
|
||||
contextId = this.getContextId(request);
|
||||
this.container.registerRequestProvider(
|
||||
contextId.getParent ? contextId.payload : request,
|
||||
contextId,
|
||||
);
|
||||
contextId = this.getContextId(request, isTreeDurable);
|
||||
dataOrContextHost = request;
|
||||
}
|
||||
const contextInstance = await this.injector.loadPerContext(
|
||||
@@ -270,7 +268,10 @@ export class ListenersController {
|
||||
return requestScopedHandler;
|
||||
}
|
||||
|
||||
private getContextId<T extends RequestContext = any>(request: T): ContextId {
|
||||
private getContextId<T extends RequestContext = any>(
|
||||
request: T,
|
||||
isTreeDurable: boolean,
|
||||
): ContextId {
|
||||
const contextId = ContextIdFactory.getByRequest(request);
|
||||
if (!request[REQUEST_CONTEXT_ID as any]) {
|
||||
Object.defineProperty(request, REQUEST_CONTEXT_ID, {
|
||||
@@ -279,10 +280,9 @@ export class ListenersController {
|
||||
writable: false,
|
||||
configurable: false,
|
||||
});
|
||||
this.container.registerRequestProvider(
|
||||
contextId.getParent ? contextId.payload : request,
|
||||
contextId,
|
||||
);
|
||||
|
||||
const requestProviderValue = isTreeDurable ? contextId.payload : request;
|
||||
this.container.registerRequestProvider(requestProviderValue, contextId);
|
||||
}
|
||||
return contextId;
|
||||
}
|
||||
|
||||
@@ -29,11 +29,13 @@ import {
|
||||
OptionsUrlencoded,
|
||||
urlencoded as bodyParserUrlencoded,
|
||||
} from 'body-parser';
|
||||
import * as bodyparser from 'body-parser';
|
||||
import * as cors from 'cors';
|
||||
import * as express from 'express';
|
||||
import * as http from 'http';
|
||||
import * as https from 'https';
|
||||
import { Duplex, pipeline } from 'stream';
|
||||
import { NestExpressBodyParserOptions } from '../interfaces/nest-express-body-parser-options.interface';
|
||||
import { ServeStaticOptions } from '../interfaces/serve-static-options.interface';
|
||||
import { getBodyParserOptions } from './utils/get-body-parser-options.util';
|
||||
|
||||
@@ -235,6 +237,19 @@ export class ExpressAdapter extends AbstractHttpAdapter {
|
||||
.forEach(parserKey => this.use(parserMiddleware[parserKey]));
|
||||
}
|
||||
|
||||
public useBodyParser<Options extends bodyparser.Options = bodyparser.Options>(
|
||||
type: keyof bodyparser.BodyParser,
|
||||
rawBody: boolean,
|
||||
options?: NestExpressBodyParserOptions<Options>,
|
||||
): this {
|
||||
const parserOptions = getBodyParserOptions(rawBody, options || {});
|
||||
const parser = bodyparser[type](parserOptions);
|
||||
|
||||
this.use(parser);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public setLocal(key: string, value: any) {
|
||||
this.instance.locals[key] = value;
|
||||
return this;
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export * from './nest-express-application.interface';
|
||||
export * from './nest-express-body-parser-options.interface';
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { Server } from 'net';
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import * as bodyparser from 'body-parser';
|
||||
import { NestExpressBodyParserOptions } from './nest-express-body-parser-options.interface';
|
||||
import { ServeStaticOptions } from './serve-static-options.interface';
|
||||
|
||||
/**
|
||||
@@ -73,6 +75,24 @@ export interface NestExpressApplication extends INestApplication {
|
||||
*/
|
||||
useStaticAssets(path: string, options?: ServeStaticOptions): this;
|
||||
|
||||
/**
|
||||
* Register Express body parsers on the fly. Will respect
|
||||
* the application's `rawBody` option.
|
||||
*
|
||||
* @example
|
||||
* const app = await NestFactory.create<NestExpressApplication>(
|
||||
* AppModule,
|
||||
* { rawBody: true }
|
||||
* );
|
||||
* app.useBodyParser('json', { limit: '50mb' });
|
||||
*
|
||||
* @returns {this}
|
||||
*/
|
||||
useBodyParser<Options extends bodyparser.Options = bodyparser.Options>(
|
||||
parser: keyof bodyparser.BodyParser,
|
||||
options?: NestExpressBodyParserOptions<Options>,
|
||||
): this;
|
||||
|
||||
/**
|
||||
* Sets one or multiple base directories for templates (views).
|
||||
*
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import type { Options } from 'body-parser';
|
||||
|
||||
export type NestExpressBodyParserOptions<T extends Options = Options> = Omit<
|
||||
T,
|
||||
'verify'
|
||||
>;
|
||||
@@ -18,9 +18,10 @@ import { isString, isUndefined } from '@nestjs/common/utils/shared.utils';
|
||||
import { AbstractHttpAdapter } from '@nestjs/core/adapters/http-adapter';
|
||||
import {
|
||||
fastify,
|
||||
FastifyBaseLogger,
|
||||
FastifyBodyParser,
|
||||
FastifyInstance,
|
||||
FastifyBaseLogger,
|
||||
FastifyListenOptions,
|
||||
FastifyPluginAsync,
|
||||
FastifyPluginCallback,
|
||||
FastifyRegister,
|
||||
@@ -45,6 +46,7 @@ import {
|
||||
} from 'light-my-request';
|
||||
// `querystring` is used internally in fastify for registering urlencoded body parser.
|
||||
import { parse as querystringParse } from 'querystring';
|
||||
import { NestFastifyBodyParserOptions } from '../interfaces';
|
||||
import {
|
||||
FastifyStaticOptions,
|
||||
FastifyViewOptions,
|
||||
@@ -227,12 +229,27 @@ export class FastifyAdapter<
|
||||
hostname: string,
|
||||
callback?: () => void,
|
||||
): void;
|
||||
public listen(port: string | number, ...args: any[]): void {
|
||||
public listen(
|
||||
listenOptions: string | number | FastifyListenOptions,
|
||||
...args: any[]
|
||||
): void {
|
||||
const isFirstArgTypeofFunction = typeof args[0] === 'function';
|
||||
const callback = isFirstArgTypeofFunction ? args[0] : args[1];
|
||||
const options: Record<string, any> = {
|
||||
port: +port,
|
||||
};
|
||||
|
||||
let options: Record<string, any>;
|
||||
if (
|
||||
typeof listenOptions === 'object' &&
|
||||
(listenOptions.host !== undefined ||
|
||||
listenOptions.port !== undefined ||
|
||||
listenOptions.path !== undefined)
|
||||
) {
|
||||
// First parameter is an object with a path, port and/or host attributes
|
||||
options = listenOptions;
|
||||
} else {
|
||||
options = {
|
||||
port: +listenOptions,
|
||||
};
|
||||
}
|
||||
if (!isFirstArgTypeofFunction) {
|
||||
options.host = args[0];
|
||||
}
|
||||
@@ -465,6 +482,43 @@ export class FastifyAdapter<
|
||||
this._isParserRegistered = true;
|
||||
}
|
||||
|
||||
public useBodyParser(
|
||||
type: string | string[] | RegExp,
|
||||
rawBody: boolean,
|
||||
options?: NestFastifyBodyParserOptions,
|
||||
parser?: FastifyBodyParser<Buffer, TServer>,
|
||||
) {
|
||||
const parserOptions = {
|
||||
...(options || {}),
|
||||
parseAs: 'buffer' as const,
|
||||
};
|
||||
|
||||
this.getInstance().addContentTypeParser<Buffer>(
|
||||
type,
|
||||
parserOptions,
|
||||
(
|
||||
req: RawBodyRequest<FastifyRequest<unknown, TServer, TRawRequest>>,
|
||||
body: Buffer,
|
||||
done,
|
||||
) => {
|
||||
if (rawBody === true && Buffer.isBuffer(body)) {
|
||||
req.rawBody = body;
|
||||
}
|
||||
|
||||
if (parser) {
|
||||
parser(req, body, done);
|
||||
return;
|
||||
}
|
||||
|
||||
done(null, body);
|
||||
},
|
||||
);
|
||||
|
||||
// To avoid the Nest application init to override our custom
|
||||
// body parser, we mark the parsers as registered.
|
||||
this._isParserRegistered = true;
|
||||
}
|
||||
|
||||
public async createMiddlewareFactory(
|
||||
requestMethod: RequestMethod,
|
||||
): Promise<(path: string, callback: Function) => any> {
|
||||
@@ -510,20 +564,15 @@ export class FastifyAdapter<
|
||||
}
|
||||
|
||||
private registerJsonContentParser(rawBody?: boolean) {
|
||||
const contentType = 'application/json';
|
||||
const withRawBody = !!rawBody;
|
||||
const { bodyLimit } = this.getInstance().initialConfig;
|
||||
|
||||
this.getInstance().addContentTypeParser<Buffer>(
|
||||
'application/json',
|
||||
{ parseAs: 'buffer', bodyLimit },
|
||||
(
|
||||
req: RawBodyRequest<FastifyRequest<unknown, TServer, TRawRequest>>,
|
||||
body: Buffer,
|
||||
done,
|
||||
) => {
|
||||
if (rawBody === true && Buffer.isBuffer(body)) {
|
||||
req.rawBody = body;
|
||||
}
|
||||
|
||||
this.useBodyParser(
|
||||
contentType,
|
||||
withRawBody,
|
||||
{ bodyLimit },
|
||||
(req, body, done) => {
|
||||
const { onProtoPoisoning, onConstructorPoisoning } =
|
||||
this.instance.initialConfig;
|
||||
const defaultJsonParser = this.instance.getDefaultJsonParser(
|
||||
@@ -536,20 +585,15 @@ export class FastifyAdapter<
|
||||
}
|
||||
|
||||
private registerUrlencodedContentParser(rawBody?: boolean) {
|
||||
const contentType = 'application/x-www-form-urlencoded';
|
||||
const withRawBody = !!rawBody;
|
||||
const { bodyLimit } = this.getInstance().initialConfig;
|
||||
|
||||
this.getInstance().addContentTypeParser<Buffer>(
|
||||
'application/x-www-form-urlencoded',
|
||||
{ parseAs: 'buffer', bodyLimit },
|
||||
(
|
||||
req: RawBodyRequest<FastifyRequest<unknown, TServer, TRawRequest>>,
|
||||
body: Buffer,
|
||||
done,
|
||||
) => {
|
||||
if (rawBody === true && Buffer.isBuffer(body)) {
|
||||
req.rawBody = body;
|
||||
}
|
||||
|
||||
this.useBodyParser(
|
||||
contentType,
|
||||
withRawBody,
|
||||
{ bodyLimit },
|
||||
(_req, body, done) => {
|
||||
done(null, querystringParse(body.toString()));
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export * from './nest-fastify-application.interface';
|
||||
export * from './nest-fastify-body-parser-options.interface';
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import {
|
||||
FastifyBodyParser,
|
||||
FastifyInstance,
|
||||
FastifyPluginAsync,
|
||||
FastifyPluginCallback,
|
||||
FastifyPluginOptions,
|
||||
FastifyRegisterOptions,
|
||||
RawServerBase,
|
||||
} from 'fastify';
|
||||
import {
|
||||
Chain as LightMyRequestChain,
|
||||
@@ -12,6 +14,7 @@ import {
|
||||
Response as LightMyRequestResponse,
|
||||
} from 'light-my-request';
|
||||
import { FastifyStaticOptions, FastifyViewOptions } from './external';
|
||||
import { NestFastifyBodyParserOptions } from './nest-fastify-body-parser-options.interface';
|
||||
|
||||
export interface NestFastifyApplication extends INestApplication {
|
||||
/**
|
||||
@@ -28,6 +31,27 @@ export interface NestFastifyApplication extends INestApplication {
|
||||
opts?: FastifyRegisterOptions<Options>,
|
||||
): Promise<FastifyInstance>;
|
||||
|
||||
/**
|
||||
* Register Fastify body parsers on the fly. Will respect
|
||||
* the application's `rawBody` option.
|
||||
*
|
||||
* @example
|
||||
* const app = await NestFactory.create<NestFastifyApplication>(
|
||||
* AppModule,
|
||||
* new FastifyAdapter(),
|
||||
* { rawBody: true }
|
||||
* );
|
||||
* // enable the json parser with a parser limit of 50mb
|
||||
* app.useBodyParser('application/json', { bodyLimit: 50 * 1000 * 1024 });
|
||||
*
|
||||
* @returns {this}
|
||||
*/
|
||||
useBodyParser<TServer extends RawServerBase = RawServerBase>(
|
||||
type: string | string[] | RegExp,
|
||||
options?: NestFastifyBodyParserOptions,
|
||||
parser?: FastifyBodyParser<Buffer, TServer>,
|
||||
): this;
|
||||
|
||||
/**
|
||||
* Sets a base directory for public assets.
|
||||
* Example `app.useStaticAssets({ root: 'public' })`
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import type { AddContentTypeParser } from 'fastify';
|
||||
|
||||
export type NestFastifyBodyParserOptions = Omit<
|
||||
Parameters<AddContentTypeParser>[1],
|
||||
'parseAs'
|
||||
>;
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Logger, LoggerService, Module } from '@nestjs/common';
|
||||
import { ModuleMetadata } from '@nestjs/common/interfaces';
|
||||
import { Logger, LoggerService, Module, ModuleMetadata } from '@nestjs/common';
|
||||
import { NestApplicationContextOptions } from '@nestjs/common/interfaces/nest-application-context-options.interface';
|
||||
import { ApplicationConfig } from '@nestjs/core/application-config';
|
||||
import { NestContainer } from '@nestjs/core/injector/container';
|
||||
|
||||
@@ -18,10 +18,11 @@ import {
|
||||
import { ExecutionContextHost } from '@nestjs/core/helpers/execution-context-host';
|
||||
import { HandlerMetadataStorage } from '@nestjs/core/helpers/handler-metadata-storage';
|
||||
import { ParamsMetadata } from '@nestjs/core/helpers/interfaces';
|
||||
import { InterceptorsConsumer } from '@nestjs/core/interceptors/interceptors-consumer';
|
||||
import { InterceptorsContextCreator } from '@nestjs/core/interceptors/interceptors-context-creator';
|
||||
import { PipesConsumer } from '@nestjs/core/pipes/pipes-consumer';
|
||||
import { PipesContextCreator } from '@nestjs/core/pipes/pipes-context-creator';
|
||||
import {
|
||||
InterceptorsConsumer,
|
||||
InterceptorsContextCreator,
|
||||
} from '@nestjs/core/interceptors';
|
||||
import { PipesConsumer, PipesContextCreator } from '@nestjs/core/pipes';
|
||||
import { PARAM_ARGS_METADATA } from '../constants';
|
||||
import { WsException } from '../errors/ws-exception';
|
||||
import { WsParamsFactory } from '../factories/ws-params-factory';
|
||||
|
||||
Reference in New Issue
Block a user