From 2ac95c8369588405af3cf833235cc05bb8b0623f Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 29 Feb 2020 09:14:32 +0900 Subject: [PATCH 1/2] fix(platform): added prefix to adapters --- .../hello-world/e2e/express-multiple.spec.ts | 36 ++++++++++--- .../hello-world/e2e/fastify-multiple.spec.ts | 53 +++++++++++++++---- .../interfaces/http/http-server.interface.ts | 4 +- packages/core/nest-application.ts | 9 ++-- packages/core/test/utils/noop-adapter.spec.ts | 4 +- .../adapters/express-adapter.ts | 33 +++++++++--- .../adapters/fastify-adapter.ts | 46 +++++++++++++--- 7 files changed, 148 insertions(+), 37 deletions(-) diff --git a/integration/hello-world/e2e/express-multiple.spec.ts b/integration/hello-world/e2e/express-multiple.spec.ts index 62516e0185a..c5fcf602b9f 100644 --- a/integration/hello-world/e2e/express-multiple.spec.ts +++ b/integration/hello-world/e2e/express-multiple.spec.ts @@ -20,7 +20,7 @@ describe('Hello world (express instance with multiple applications)', () => { const adapter = new ExpressAdapter(express()); apps = [ - module1.createNestApplication(adapter), + module1.createNestApplication(adapter).setGlobalPrefix('app1'), module2.createNestApplication(adapter).setGlobalPrefix('/app2'), ]; await Promise.all(apps.map(app => app.init())); @@ -28,9 +28,9 @@ describe('Hello world (express instance with multiple applications)', () => { server = adapter.getInstance(); }); - it(`/GET`, () => { + it(`/GET (app1)`, () => { return request(server) - .get('/hello') + .get('/app1/hello') .expect(200) .expect('Hello world!'); }); @@ -42,9 +42,9 @@ describe('Hello world (express instance with multiple applications)', () => { .expect('Hello world!'); }); - it(`/GET (Promise/async)`, () => { + it(`/GET (app1 Promise/async)`, () => { return request(server) - .get('/hello/async') + .get('/app1/hello/async') .expect(200) .expect('Hello world!'); }); @@ -56,9 +56,9 @@ describe('Hello world (express instance with multiple applications)', () => { .expect('Hello world!'); }); - it(`/GET (Observable stream)`, () => { + it(`/GET (app1 Observable stream)`, () => { return request(server) - .get('/hello/stream') + .get('/app1/hello/stream') .expect(200) .expect('Hello world!'); }); @@ -70,6 +70,28 @@ describe('Hello world (express instance with multiple applications)', () => { .expect('Hello world!'); }); + it(`/GET (app1 NotFound)`, () => { + return request(server) + .get('/app1/cats') + .expect(404) + .expect({ + statusCode: 404, + error: 'Not Found', + message: 'Cannot GET /cats', + }); + }); + + it(`/GET (app2 NotFound)`, () => { + return request(server) + .get('/app2/cats') + .expect(404) + .expect({ + statusCode: 404, + error: 'Not Found', + message: 'Cannot GET /cats', + }); + }); + afterEach(async () => { await Promise.all(apps.map(app => app.close())); }); diff --git a/integration/hello-world/e2e/fastify-multiple.spec.ts b/integration/hello-world/e2e/fastify-multiple.spec.ts index d8dc60b1043..fa6a2ed81a1 100644 --- a/integration/hello-world/e2e/fastify-multiple.spec.ts +++ b/integration/hello-world/e2e/fastify-multiple.spec.ts @@ -1,8 +1,7 @@ -/* Temporarily disabled due to various regressions - import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify'; import { Test } from '@nestjs/testing'; import { expect } from 'chai'; +import { fail } from 'assert'; import { ApplicationModule } from '../src/app.module'; describe('Hello world (fastify adapter with multiple applications)', () => { @@ -20,7 +19,7 @@ describe('Hello world (fastify adapter with multiple applications)', () => { adapter = new FastifyAdapter(); apps = [ - module1.createNestApplication(adapter), + module1.createNestApplication(adapter).setGlobalPrefix('app1'), module2 .createNestApplication(adapter, { bodyParser: false, @@ -30,11 +29,11 @@ describe('Hello world (fastify adapter with multiple applications)', () => { await Promise.all(apps.map(app => app.init())); }); - it(`/GET`, () => { + it(`/GET (app1)`, () => { return adapter .inject({ method: 'GET', - url: '/hello', + url: '/app1/hello', }) .then(({ payload }) => expect(payload).to.be.eql('Hello world!')); }); @@ -48,11 +47,11 @@ describe('Hello world (fastify adapter with multiple applications)', () => { .then(({ payload }) => expect(payload).to.be.eql('Hello world!')); }); - it(`/GET (Promise/async)`, () => { + it(`/GET (app1 Promise/async)`, () => { return adapter .inject({ method: 'GET', - url: '/hello/async', + url: '/app1/hello/async', }) .then(({ payload }) => expect(payload).to.be.eql('Hello world!')); }); @@ -66,11 +65,11 @@ describe('Hello world (fastify adapter with multiple applications)', () => { .then(({ payload }) => expect(payload).to.be.eql('Hello world!')); }); - it(`/GET (Observable stream)`, () => { + it(`/GET (app1 Observable stream)`, () => { return adapter .inject({ method: 'GET', - url: '/hello/stream', + url: '/app1/hello/stream', }) .then(({ payload }) => expect(payload).to.be.eql('Hello world!')); }); @@ -84,8 +83,42 @@ describe('Hello world (fastify adapter with multiple applications)', () => { .then(({ payload }) => expect(payload).to.be.eql('Hello world!')); }); + it(`/GET (app1 NotFound)`, () => { + return adapter + .inject({ + method: 'GET', + url: '/app1/cats', + }) + .then( + ({ payload }) => { + expect(payload).to.be.eql(JSON.stringify({ + statusCode: 404, + error: 'Not Found', + message: 'Cannot GET /app1/cats', + })); + }, + ); + }); + + it(`/GET (app2 NotFound)`, () => { + return adapter + .inject({ + method: 'GET', + url: '/app2/cats', + }) + .then( + ({ payload }) => { + expect(payload).to.be.eql(JSON.stringify({ + statusCode: 404, + error: 'Not Found', + message: 'Cannot GET /app2/cats', + })); + }, + ); + }); + afterEach(async () => { await Promise.all(apps.map(app => app.close())); await adapter.close(); }); -});*/ +}); diff --git a/packages/common/interfaces/http/http-server.interface.ts b/packages/common/interfaces/http/http-server.interface.ts index b8107b31673..835444e7fed 100644 --- a/packages/common/interfaces/http/http-server.interface.ts +++ b/packages/common/interfaces/http/http-server.interface.ts @@ -59,8 +59,8 @@ export interface HttpServer { getRequestMethod?(request: TRequest): string; getRequestUrl?(request: TResponse): string; getInstance(): any; - registerParserMiddleware(): any; - enableCors(options: CorsOptions): any; + registerParserMiddleware(prefix?: string): any; + enableCors(options: CorsOptions, prefix?: string): any; getHttpServer(): any; initHttpServer(options: NestApplicationOptions): void; close(): any; diff --git a/packages/core/nest-application.ts b/packages/core/nest-application.ts index 160ff2f8904..9c70500ed7e 100644 --- a/packages/core/nest-application.ts +++ b/packages/core/nest-application.ts @@ -140,7 +140,8 @@ export class NestApplication extends NestApplicationContext const useBodyParser = this.appOptions && this.appOptions.bodyParser !== false; - useBodyParser && this.registerParserMiddleware(); + useBodyParser && + this.registerParserMiddleware(this.config.getGlobalPrefix()); await this.registerModules(); await this.registerRouter(); @@ -153,8 +154,8 @@ export class NestApplication extends NestApplicationContext return this; } - public registerParserMiddleware() { - this.httpAdapter.registerParserMiddleware(); + public registerParserMiddleware(prefix?: string) { + this.httpAdapter.registerParserMiddleware(prefix); } public async registerRouter() { @@ -216,7 +217,7 @@ export class NestApplication extends NestApplicationContext } public enableCors(options?: CorsOptions): void { - this.httpAdapter.enableCors(options); + this.httpAdapter.enableCors(options, this.config.getGlobalPrefix()); } public async listen( diff --git a/packages/core/test/utils/noop-adapter.spec.ts b/packages/core/test/utils/noop-adapter.spec.ts index c2e73d09fd6..fb9b7afec15 100644 --- a/packages/core/test/utils/noop-adapter.spec.ts +++ b/packages/core/test/utils/noop-adapter.spec.ts @@ -19,8 +19,8 @@ export class NoopHttpAdapter extends AbstractHttpAdapter { setErrorHandler(handler: Function, prefix = '/'): any {} setNotFoundHandler(handler: Function, prefix = '/'): any {} setHeader(response: any, name: string, value: string): any {} - registerParserMiddleware(): any {} - enableCors(options: any): any {} + registerParserMiddleware(prefix?: string): any {} + enableCors(options: any, prefix?: string): any {} createMiddlewareFactory(requestMethod: RequestMethod): any {} getType() { return ''; diff --git a/packages/platform-express/adapters/express-adapter.ts b/packages/platform-express/adapters/express-adapter.ts index cea4d1bb1e0..2b576046a5f 100644 --- a/packages/platform-express/adapters/express-adapter.ts +++ b/packages/platform-express/adapters/express-adapter.ts @@ -41,11 +41,17 @@ export class ExpressAdapter extends AbstractHttpAdapter { } public setErrorHandler(handler: Function, prefix?: string) { - return this.use(handler); + if (!prefix) { + return this.use(handler); + } + return this.use(prefix.charAt(0) !== '/' ? '/' + prefix : prefix, handler); } public setNotFoundHandler(handler: Function, prefix?: string) { - return this.use(handler); + if (!prefix) { + return this.use(handler); + } + return this.use(prefix.charAt(0) !== '/' ? '/' + prefix : prefix, handler); } public setHeader(response: any, name: string, value: string) { @@ -108,8 +114,14 @@ export class ExpressAdapter extends AbstractHttpAdapter { return request.url; } - public enableCors(options: CorsOptions) { - return this.use(cors(options)); + public enableCors(options: CorsOptions, prefix?: string) { + if (!prefix) { + return this.use(cors(options)); + } + return this.use( + prefix.charAt(0) !== '/' ? '/' + prefix : prefix, + cors(options), + ); } public createMiddlewareFactory( @@ -132,14 +144,23 @@ export class ExpressAdapter extends AbstractHttpAdapter { this.httpServer = http.createServer(this.getInstance()); } - public registerParserMiddleware() { + public registerParserMiddleware(prefix?: string) { const parserMiddleware = { jsonParser: bodyParser.json(), urlencodedParser: bodyParser.urlencoded({ extended: true }), }; Object.keys(parserMiddleware) .filter(parser => !this.isMiddlewareApplied(parser)) - .forEach(parserKey => this.use(parserMiddleware[parserKey])); + .forEach(parserKey => { + if (prefix) { + this.use( + prefix.charAt(0) !== '/' ? '/' + prefix : prefix, + parserMiddleware[parserKey], + ); + } else { + this.use(parserMiddleware[parserKey]); + } + }); } public getType(): string { diff --git a/packages/platform-fastify/adapters/fastify-adapter.ts b/packages/platform-fastify/adapters/fastify-adapter.ts index b433e03c141..5d651b6c746 100644 --- a/packages/platform-fastify/adapters/fastify-adapter.ts +++ b/packages/platform-fastify/adapters/fastify-adapter.ts @@ -70,14 +70,30 @@ export class FastifyAdapter extends AbstractHttpAdapter { handler: Parameters[0], prefix?: string, ) { - return this.instance.setErrorHandler(handler); + if (!prefix) { + return this.instance.setErrorHandler(handler); + } + return this.registerWithPrefix( + async (instance: fastify.FastifyInstance): Promise => { + instance.setErrorHandler(handler); + }, + prefix.charAt(0) !== '/' ? '/' + prefix : prefix, + ); } public setNotFoundHandler( handler: Parameters[0], prefix?: string, ) { - return this.instance.setNotFoundHandler(handler); + if (!prefix) { + return this.instance.setNotFoundHandler(handler); + } + return this.registerWithPrefix( + async (instance: fastify.FastifyInstance): Promise => { + instance.setNotFoundHandler(handler); + }, + prefix.charAt(0) !== '/' ? '/' + prefix : prefix, + ); } public getHttpServer(): TServer { @@ -142,12 +158,30 @@ export class FastifyAdapter extends AbstractHttpAdapter { return request.raw.url; } - public enableCors(options: CorsOptions) { - this.register(cors, options); + public enableCors(options: CorsOptions, prefix?: string) { + if (!prefix) { + this.register(cors, options); + return; + } + this.registerWithPrefix( + async (instance: fastify.FastifyInstance): Promise => { + instance.register(cors, (options as unknown) as {}); + }, + prefix.charAt(0) !== '/' ? '/' + prefix : prefix, + ); } - public registerParserMiddleware() { - this.register(formBody); + public registerParserMiddleware(prefix?: string) { + if (!prefix) { + this.register(formBody); + return; + } + this.registerWithPrefix( + async (instance: fastify.FastifyInstance): Promise => { + instance.register(formBody); + }, + prefix.charAt(0) !== '/' ? '/' + prefix : prefix, + ); } public createMiddlewareFactory( From b432140d2985f5a9e6ea489dd8cb60cfab3f92ef Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Mon, 2 Mar 2020 20:27:33 +0900 Subject: [PATCH 2/2] fix(platform): support notfound in root url when added prefix --- .../hello-world/e2e/express-multiple.spec.ts | 15 ++++ .../hello-world/e2e/fastify-multiple.spec.ts | 21 ++++++ packages/core/adapters/http-adapter.ts | 4 ++ packages/core/router/routes-resolver.ts | 20 ++++-- .../core/test/router/routes-resolver.spec.ts | 22 ++++++ packages/core/test/utils/noop-adapter.spec.ts | 5 ++ .../adapters/express-adapter.ts | 69 +++++++++++++++++-- .../adapters/fastify-adapter.ts | 65 ++++++++++++++--- 8 files changed, 201 insertions(+), 20 deletions(-) diff --git a/integration/hello-world/e2e/express-multiple.spec.ts b/integration/hello-world/e2e/express-multiple.spec.ts index c5fcf602b9f..3020cad2fe4 100644 --- a/integration/hello-world/e2e/express-multiple.spec.ts +++ b/integration/hello-world/e2e/express-multiple.spec.ts @@ -16,12 +16,16 @@ describe('Hello world (express instance with multiple applications)', () => { const module2 = await Test.createTestingModule({ imports: [ApplicationModule], }).compile(); + const module3 = await Test.createTestingModule({ + imports: [ApplicationModule], + }).compile(); const adapter = new ExpressAdapter(express()); apps = [ module1.createNestApplication(adapter).setGlobalPrefix('app1'), module2.createNestApplication(adapter).setGlobalPrefix('/app2'), + module3.createNestApplication(adapter), ]; await Promise.all(apps.map(app => app.init())); @@ -92,6 +96,17 @@ describe('Hello world (express instance with multiple applications)', () => { }); }); + it(`/GET (app3 NotFound)`, () => { + return request(server) + .get('/app3/cats') + .expect(404) + .expect({ + statusCode: 404, + error: 'Not Found', + message: 'Cannot GET /app3/cats', + }); + }); + afterEach(async () => { await Promise.all(apps.map(app => app.close())); }); diff --git a/integration/hello-world/e2e/fastify-multiple.spec.ts b/integration/hello-world/e2e/fastify-multiple.spec.ts index fa6a2ed81a1..714159d0fbd 100644 --- a/integration/hello-world/e2e/fastify-multiple.spec.ts +++ b/integration/hello-world/e2e/fastify-multiple.spec.ts @@ -15,6 +15,9 @@ describe('Hello world (fastify adapter with multiple applications)', () => { const module2 = await Test.createTestingModule({ imports: [ApplicationModule], }).compile(); + const module3 = await Test.createTestingModule({ + imports: [ApplicationModule], + }).compile(); adapter = new FastifyAdapter(); @@ -25,6 +28,7 @@ describe('Hello world (fastify adapter with multiple applications)', () => { bodyParser: false, }) .setGlobalPrefix('/app2'), + module3.createNestApplication(adapter), ]; await Promise.all(apps.map(app => app.init())); }); @@ -117,6 +121,23 @@ describe('Hello world (fastify adapter with multiple applications)', () => { ); }); + it(`/GET (app3 NotFound)`, () => { + return adapter + .inject({ + method: 'GET', + url: '/app3/cats', + }) + .then( + ({ payload }) => { + expect(payload).to.be.eql(JSON.stringify({ + statusCode: 404, + error: 'Not Found', + message: 'Cannot GET /app3/cats', + })); + }, + ); + }); + afterEach(async () => { await Promise.all(apps.map(app => app.close())); await adapter.close(); diff --git a/packages/core/adapters/http-adapter.ts b/packages/core/adapters/http-adapter.ts index 8039dda3989..2b10295aa45 100644 --- a/packages/core/adapters/http-adapter.ts +++ b/packages/core/adapters/http-adapter.ts @@ -92,6 +92,7 @@ export abstract class AbstractHttpAdapter< abstract redirect(response, statusCode: number, url: string); abstract setErrorHandler(handler: Function, prefix?: string); abstract setNotFoundHandler(handler: Function, prefix?: string); + abstract setRootNotFoundHandler(handler: Function); abstract setHeader(response, name: string, value: string); abstract registerParserMiddleware(prefix?: string); abstract enableCors(options: CorsOptions, prefix?: string); @@ -99,4 +100,7 @@ export abstract class AbstractHttpAdapter< requestMethod: RequestMethod, ): (path: string, callback: Function) => any; abstract getType(): string; + abstract addNestInstanceBaseUrl(baseUrl?: string): void; + abstract getNotFoundCallback(baseUrl?: string); + abstract getRootNotFoundCallback(); } diff --git a/packages/core/router/routes-resolver.ts b/packages/core/router/routes-resolver.ts index 737e1bf8061..52236ce4708 100644 --- a/packages/core/router/routes-resolver.ts +++ b/packages/core/router/routes-resolver.ts @@ -78,15 +78,25 @@ export class RoutesResolver implements Resolver { public registerNotFoundHandler() { const applicationRef = this.container.getHttpAdapterRef(); - const callback = (req: TRequest, res: TResponse) => { - const method = applicationRef.getRequestMethod(req); - const url = applicationRef.getRequestUrl(req); - throw new NotFoundException(`Cannot ${method} ${url}`); - }; + applicationRef.addNestInstanceBaseUrl(this.config.getGlobalPrefix()); + + const callback = applicationRef.getNotFoundCallback( + this.config.getGlobalPrefix(), + ); const handler = this.routerExceptionsFilter.create({}, callback, undefined); const proxy = this.routerProxy.createProxy(callback, handler); applicationRef.setNotFoundHandler && applicationRef.setNotFoundHandler(proxy, this.config.getGlobalPrefix()); + + const rootCallback = applicationRef.getRootNotFoundCallback(); + const rootHandler = this.routerExceptionsFilter.create( + {}, + rootCallback, + undefined, + ); + const rootProxy = this.routerProxy.createProxy(rootCallback, rootHandler); + applicationRef.setRootNotFoundHandler && + applicationRef.setRootNotFoundHandler(rootProxy); } public registerExceptionHandler() { diff --git a/packages/core/test/router/routes-resolver.spec.ts b/packages/core/test/router/routes-resolver.spec.ts index cdbf3de9e1f..55b9bca04eb 100644 --- a/packages/core/test/router/routes-resolver.spec.ts +++ b/packages/core/test/router/routes-resolver.spec.ts @@ -251,6 +251,28 @@ describe('RoutesResolver', () => { describe('registerNotFoundHandler', () => { it('should register not found handler', () => { + let applicationRef = { + addNestInstanceBaseUrl: (baseUrl: string) => { + return; + }, + getNotFoundCallback: (baseUrl?: string) => { + return (req, res, next) => { + next(); + }; + }, + getRootNotFoundCallback: () => { + return (req, res, next) => { + next(); + }; + }, + setNotFoundHandler: sinon.spy(), + setRootNotFoundHandler: sinon.spy(), + }; + + sinon + .stub((routesResolver as any).container, 'getHttpAdapterRef') + .callsFake(() => applicationRef); + routesResolver.registerNotFoundHandler(); expect(applicationRef.setNotFoundHandler.called).to.be.true; diff --git a/packages/core/test/utils/noop-adapter.spec.ts b/packages/core/test/utils/noop-adapter.spec.ts index fb9b7afec15..c17400474ba 100644 --- a/packages/core/test/utils/noop-adapter.spec.ts +++ b/packages/core/test/utils/noop-adapter.spec.ts @@ -1,5 +1,6 @@ import { RequestMethod } from '@nestjs/common'; import { AbstractHttpAdapter } from '../../adapters'; +import { Func } from 'mocha'; export class NoopHttpAdapter extends AbstractHttpAdapter { constructor(instance: any) { @@ -18,6 +19,7 @@ export class NoopHttpAdapter extends AbstractHttpAdapter { redirect(response: any, statusCode: number, url: string) {} setErrorHandler(handler: Function, prefix = '/'): any {} setNotFoundHandler(handler: Function, prefix = '/'): any {} + setRootNotFoundHandler(handler: Function): any {} setHeader(response: any, name: string, value: string): any {} registerParserMiddleware(prefix?: string): any {} enableCors(options: any, prefix?: string): any {} @@ -25,4 +27,7 @@ export class NoopHttpAdapter extends AbstractHttpAdapter { getType() { return ''; } + addNestInstanceBaseUrl(baseUrl?: string): void {} + getNotFoundCallback(baseUrl?: string) {} + getRootNotFoundCallback() {} } diff --git a/packages/platform-express/adapters/express-adapter.ts b/packages/platform-express/adapters/express-adapter.ts index 2b576046a5f..68e2df44ef2 100644 --- a/packages/platform-express/adapters/express-adapter.ts +++ b/packages/platform-express/adapters/express-adapter.ts @@ -1,4 +1,4 @@ -import { RequestMethod } from '@nestjs/common'; +import { RequestMethod, NotFoundException } from '@nestjs/common'; import { CorsOptions } from '@nestjs/common/interfaces/external/cors-options.interface'; import { NestApplicationOptions } from '@nestjs/common/interfaces/nest-application-options.interface'; import { isFunction, isNil, isObject } from '@nestjs/common/utils/shared.utils'; @@ -13,6 +13,7 @@ import { ServeStaticOptions } from '../interfaces/serve-static-options.interface export class ExpressAdapter extends AbstractHttpAdapter { private readonly routerMethodFactory = new RouterMethodFactory(); + private nestInstanceBaseUrlMap: string[] = []; constructor(instance?: any) { super(instance || express()); @@ -47,11 +48,13 @@ export class ExpressAdapter extends AbstractHttpAdapter { return this.use(prefix.charAt(0) !== '/' ? '/' + prefix : prefix, handler); } - public setNotFoundHandler(handler: Function, prefix?: string) { - if (!prefix) { - return this.use(handler); - } - return this.use(prefix.charAt(0) !== '/' ? '/' + prefix : prefix, handler); + public setNotFoundHandler(handler: Function, prefix = '/') { + const baseUrl = prefix.charAt(0) !== '/' ? '/' + prefix : prefix; + return this.use(baseUrl, handler); + } + + public setRootNotFoundHandler(handler: Function) { + return this.use('/', handler); } public setHeader(response: any, name: string, value: string) { @@ -114,6 +117,10 @@ export class ExpressAdapter extends AbstractHttpAdapter { return request.url; } + private getRequestOriginalUrl(request: any): string { + return request.originalUrl; + } + public enableCors(options: CorsOptions, prefix?: string) { if (!prefix) { return this.use(cors(options)); @@ -167,6 +174,56 @@ export class ExpressAdapter extends AbstractHttpAdapter { return 'express'; } + public addNestInstanceBaseUrl(baseUrl?: string) { + if (!baseUrl) { + this.nestInstanceBaseUrlMap.push('/'); + return; + } + this.nestInstanceBaseUrlMap.push( + baseUrl.charAt(0) !== '/' ? '/' + baseUrl : baseUrl, + ); + } + + public getNotFoundCallback(baseUrl = '/') { + const prefix = baseUrl.charAt(0) !== '/' ? '/' + baseUrl : baseUrl; + return ( + req: TRequest, + res: TResponse, + next: Function, + ) => { + const method = this.getRequestMethod(req); + const url = this.getRequestUrl(req); + const originalUrl = this.getRequestOriginalUrl(req); + const matchBaseUrl = this.nestInstanceBaseUrlMap + .filter( + nestInstanceBaseUrl => originalUrl.indexOf(nestInstanceBaseUrl) == 0, + ) + .reduce((a, b) => (a.length > b.length ? a : b)); + + if (matchBaseUrl === prefix) { + throw new NotFoundException(`Cannot ${method} ${url}`); + } + next(); + }; + } + + public getRootNotFoundCallback() { + const thisInstanceNo = this.nestInstanceBaseUrlMap.length; + return ( + req: TRequest, + res: TResponse, + next: Function, + ) => { + const method = this.getRequestMethod(req); + const url = this.getRequestUrl(req); + + if (thisInstanceNo === this.nestInstanceBaseUrlMap.length) { + throw new NotFoundException(`Cannot ${method} ${url}`); + } + next(); + }; + } + private isMiddlewareApplied(name: string): boolean { const app = this.getInstance(); return ( diff --git a/packages/platform-fastify/adapters/fastify-adapter.ts b/packages/platform-fastify/adapters/fastify-adapter.ts index 5d651b6c746..9d8bad66840 100644 --- a/packages/platform-fastify/adapters/fastify-adapter.ts +++ b/packages/platform-fastify/adapters/fastify-adapter.ts @@ -1,4 +1,4 @@ -import { HttpStatus, RequestMethod } from '@nestjs/common'; +import { HttpStatus, RequestMethod, NotFoundException } from '@nestjs/common'; import { CorsOptions } from '@nestjs/common/interfaces/external/cors-options.interface'; import { NestApplicationOptions } from '@nestjs/common/interfaces/nest-application-options.interface'; import { loadPackage } from '@nestjs/common/utils/load-package.util'; @@ -10,6 +10,9 @@ import * as Reply from 'fastify/lib/reply'; import * as pathToRegexp from 'path-to-regexp'; export class FastifyAdapter extends AbstractHttpAdapter { + private nestInstanceCount; + private nestInstanceBaseUrlMap: string[]; + constructor( instanceOrOptions: | TInstance @@ -27,6 +30,7 @@ export class FastifyAdapter extends AbstractHttpAdapter { : fastify((instanceOrOptions as any) as fastify.ServerOptions); super(instance); + this.nestInstanceBaseUrlMap = []; } public listen(port: string | number, callback?: () => void); @@ -83,19 +87,30 @@ export class FastifyAdapter extends AbstractHttpAdapter { public setNotFoundHandler( handler: Parameters[0], - prefix?: string, + prefix = '/', ) { - if (!prefix) { - return this.instance.setNotFoundHandler(handler); - } - return this.registerWithPrefix( - async (instance: fastify.FastifyInstance): Promise => { + const baseUrl = prefix.charAt(0) !== '/' ? '/' + prefix : prefix; + return this.instance.register( + (instance, options, done) => { instance.setNotFoundHandler(handler); + done(); }, - prefix.charAt(0) !== '/' ? '/' + prefix : prefix, + { prefix: baseUrl }, ); } + public setRootNotFoundHandler(handler: Function) { + if (!this.nestInstanceBaseUrlMap.includes('/')) { + return this.instance.register( + (instance, options, done) => { + instance.setNotFoundHandler(handler); + done(); + }, + { prefix: '/' }, + ); + } + } + public getHttpServer(): TServer { return this.instance.server as TServer; } @@ -216,10 +231,42 @@ export class FastifyAdapter extends AbstractHttpAdapter { return 'fastify'; } + public addNestInstanceBaseUrl(baseUrl?: string) { + if (!baseUrl) { + this.nestInstanceBaseUrlMap.push('/'); + return; + } + this.nestInstanceBaseUrlMap.push( + baseUrl.charAt(0) !== '/' ? '/' + baseUrl : baseUrl, + ); + } + + public getNotFoundCallback(baseUrl?: string): Function { + return (req, res, next) => { + const method = this.getRequestMethod(req); + const url = this.getRequestUrl(req); + throw new NotFoundException(`Cannot ${method} ${url}`); + }; + } + + public getRootNotFoundCallback() { + return (req, res, next) => { + const method = this.getRequestMethod(req); + const url = this.getRequestUrl(req); + throw new NotFoundException(`Cannot ${method} ${url}`); + }; + } + protected registerWithPrefix>( factory: T, prefix = '/', ): ReturnType { - return this.instance.register(factory, { prefix }); + return this.instance.register( + (instance, options, done) => { + factory; + done(); + }, + { prefix }, + ); } }