From 83c652c7d1e0d2a292c631c60f6dc7d13332e7ba Mon Sep 17 00:00:00 2001 From: Vasil Rangelov Date: Tue, 17 Dec 2019 11:55:19 +0200 Subject: [PATCH] feature(http-adapter) Added the application's global prefix to the error and not found handlers, cors and body parser registrations, and enabled the adapters to register them based on prefix. --- .../hello-world/e2e/express-multiple.spec.ts | 76 ++++++++++++++++ .../hello-world/e2e/fastify-multiple.spec.ts | 90 +++++++++++++++++++ .../interfaces/http/http-server.interface.ts | 8 +- .../interfaces/nest-application.interface.ts | 2 +- packages/core/adapters/http-adapter.ts | 8 +- packages/core/nest-application.ts | 16 ++-- packages/core/router/routes-resolver.ts | 4 +- packages/core/test/utils/noop-adapter.spec.ts | 8 +- .../adapters/express-adapter.ts | 16 ++-- .../adapters/fastify-adapter.ts | 57 ++++++++++-- 10 files changed, 246 insertions(+), 39 deletions(-) create mode 100644 integration/hello-world/e2e/express-multiple.spec.ts create mode 100644 integration/hello-world/e2e/fastify-multiple.spec.ts diff --git a/integration/hello-world/e2e/express-multiple.spec.ts b/integration/hello-world/e2e/express-multiple.spec.ts new file mode 100644 index 00000000000..62516e0185a --- /dev/null +++ b/integration/hello-world/e2e/express-multiple.spec.ts @@ -0,0 +1,76 @@ +import { INestApplication } from '@nestjs/common'; +import { ExpressAdapter } from '@nestjs/platform-express'; +import { Test } from '@nestjs/testing'; +import * as express from 'express'; +import * as request from 'supertest'; +import { ApplicationModule } from '../src/app.module'; + +describe('Hello world (express instance with multiple applications)', () => { + let server; + let apps: INestApplication[]; + + beforeEach(async () => { + const module1 = await Test.createTestingModule({ + imports: [ApplicationModule], + }).compile(); + const module2 = await Test.createTestingModule({ + imports: [ApplicationModule], + }).compile(); + + const adapter = new ExpressAdapter(express()); + + apps = [ + module1.createNestApplication(adapter), + module2.createNestApplication(adapter).setGlobalPrefix('/app2'), + ]; + await Promise.all(apps.map(app => app.init())); + + server = adapter.getInstance(); + }); + + it(`/GET`, () => { + return request(server) + .get('/hello') + .expect(200) + .expect('Hello world!'); + }); + + it(`/GET (app2)`, () => { + return request(server) + .get('/app2/hello') + .expect(200) + .expect('Hello world!'); + }); + + it(`/GET (Promise/async)`, () => { + return request(server) + .get('/hello/async') + .expect(200) + .expect('Hello world!'); + }); + + it(`/GET (app2 Promise/async)`, () => { + return request(server) + .get('/app2/hello/async') + .expect(200) + .expect('Hello world!'); + }); + + it(`/GET (Observable stream)`, () => { + return request(server) + .get('/hello/stream') + .expect(200) + .expect('Hello world!'); + }); + + it(`/GET (app2 Observable stream)`, () => { + return request(server) + .get('/app2/hello/stream') + .expect(200) + .expect('Hello world!'); + }); + + 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 new file mode 100644 index 00000000000..6f363ec6b8a --- /dev/null +++ b/integration/hello-world/e2e/fastify-multiple.spec.ts @@ -0,0 +1,90 @@ +import { + FastifyAdapter, + NestFastifyApplication, +} from '@nestjs/platform-fastify'; +import { Test } from '@nestjs/testing'; +import { expect } from 'chai'; +import { ApplicationModule } from '../src/app.module'; + +describe('Hello world (fastify adapter with multiple applications)', () => { + let adapter: FastifyAdapter; + let apps: NestFastifyApplication[]; + + beforeEach(async () => { + const module1 = await Test.createTestingModule({ + imports: [ApplicationModule], + }).compile(); + const module2 = await Test.createTestingModule({ + imports: [ApplicationModule], + }).compile(); + + adapter = new FastifyAdapter(); + + apps = [ + module1.createNestApplication(adapter), + module2 + .createNestApplication(adapter) + .setGlobalPrefix('/app2'), + ]; + await Promise.all(apps.map(app => app.init())); + }); + + it(`/GET`, () => { + return adapter + .inject({ + method: 'GET', + url: '/hello', + }) + .then(({ payload }) => expect(payload).to.be.eql('Hello world!')); + }); + + it(`/GET (app2)`, () => { + return adapter + .inject({ + method: 'GET', + url: '/app2/hello', + }) + .then(({ payload }) => expect(payload).to.be.eql('Hello world!')); + }); + + it(`/GET (Promise/async)`, () => { + return adapter + .inject({ + method: 'GET', + url: '/hello/async', + }) + .then(({ payload }) => expect(payload).to.be.eql('Hello world!')); + }); + + it(`/GET (app2 Promise/async)`, () => { + return adapter + .inject({ + method: 'GET', + url: '/app2/hello/async', + }) + .then(({ payload }) => expect(payload).to.be.eql('Hello world!')); + }); + + it(`/GET (Observable stream)`, () => { + return adapter + .inject({ + method: 'GET', + url: '/hello/stream', + }) + .then(({ payload }) => expect(payload).to.be.eql('Hello world!')); + }); + + it(`/GET (app2 Observable stream)`, () => { + return adapter + .inject({ + method: 'GET', + url: '/app2/hello/stream', + }) + .then(({ payload }) => expect(payload).to.be.eql('Hello world!')); + }); + + 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 94984d38b81..9ba2ab79182 100644 --- a/packages/common/interfaces/http/http-server.interface.ts +++ b/packages/common/interfaces/http/http-server.interface.ts @@ -47,8 +47,8 @@ export interface HttpServer { render(response: any, view: string, options: any): any; redirect(response: any, statusCode: number, url: string): any; setHeader(response: any, name: string, value: string): any; - setErrorHandler?(handler: Function): any; - setNotFoundHandler?(handler: Function): any; + setErrorHandler?(handler: Function, prefix?: string): any; + setNotFoundHandler?(handler: Function, prefix?: string): any; useStaticAssets?(...args: any[]): this; setBaseViewsDir?(path: string | string[]): this; setViewEngine?(engineOrOptions: any): this; @@ -58,8 +58,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/common/interfaces/nest-application.interface.ts b/packages/common/interfaces/nest-application.interface.ts index 0c19dc2ac17..f4de088347f 100644 --- a/packages/common/interfaces/nest-application.interface.ts +++ b/packages/common/interfaces/nest-application.interface.ts @@ -26,7 +26,7 @@ export interface INestApplication extends INestApplicationContext { * * @returns {void} */ - enableCors(options?: CorsOptions): this; + enableCors(options?: CorsOptions): void; /** * Starts the application. diff --git a/packages/core/adapters/http-adapter.ts b/packages/core/adapters/http-adapter.ts index 87e51a5ef1e..a4e036102f0 100644 --- a/packages/core/adapters/http-adapter.ts +++ b/packages/core/adapters/http-adapter.ts @@ -88,11 +88,11 @@ export abstract class AbstractHttpAdapter< abstract reply(response, body: any, statusCode?: number); abstract render(response, view: string, options: any); abstract redirect(response, statusCode: number, url: string); - abstract setErrorHandler(handler: Function); - abstract setNotFoundHandler(handler: Function); + abstract setErrorHandler(handler: Function, prefix?: string); + abstract setNotFoundHandler(handler: Function, prefix?: string); abstract setHeader(response, name: string, value: string); - abstract registerParserMiddleware(); - abstract enableCors(options: CorsOptions); + abstract registerParserMiddleware(prefix?: string); + abstract enableCors(options: CorsOptions, prefix?: string); abstract createMiddlewareFactory( requestMethod: RequestMethod, ): (path: string, callback: Function) => any; diff --git a/packages/core/nest-application.ts b/packages/core/nest-application.ts index 1434a124cc4..4b89829a51b 100644 --- a/packages/core/nest-application.ts +++ b/packages/core/nest-application.ts @@ -62,7 +62,6 @@ export class NestApplication extends NestApplicationContext ) { super(container); - this.applyOptions(); this.selectContextModule(); this.registerHttpServer(); @@ -105,7 +104,7 @@ export class NestApplication extends NestApplicationContext if (!isCorsOptionsObj) { return this.enableCors(); } - this.enableCors(this.appOptions.cors as CorsOptions); + return this.enableCors(this.appOptions.cors as CorsOptions); } public createServer(): T { @@ -136,9 +135,11 @@ export class NestApplication extends NestApplicationContext } public async init(): Promise { + this.applyOptions(); + const useBodyParser = this.appOptions && this.appOptions.bodyParser !== false; - useBodyParser && this.registerParserMiddleware(); + useBodyParser && this.registerParserMiddleware(this.config.getGlobalPrefix()); await this.registerModules(); await this.registerRouter(); @@ -151,8 +152,8 @@ export class NestApplication extends NestApplicationContext return this; } - public registerParserMiddleware() { - this.httpAdapter.registerParserMiddleware(); + public registerParserMiddleware(prefix: string = '/') { + this.httpAdapter.registerParserMiddleware(prefix); } public async registerRouter() { @@ -213,9 +214,8 @@ export class NestApplication extends NestApplicationContext return this; } - public enableCors(options?: CorsOptions): this { - this.httpAdapter.enableCors(options); - return this; + public enableCors(options?: CorsOptions): void { + this.httpAdapter.enableCors(options, this.config.getGlobalPrefix()); } public async listen( diff --git a/packages/core/router/routes-resolver.ts b/packages/core/router/routes-resolver.ts index 727cad6f1b3..426d049fe73 100644 --- a/packages/core/router/routes-resolver.ts +++ b/packages/core/router/routes-resolver.ts @@ -86,7 +86,7 @@ export class RoutesResolver implements Resolver { const handler = this.routerExceptionsFilter.create({}, callback, undefined); const proxy = this.routerProxy.createProxy(callback, handler); applicationRef.setNotFoundHandler && - applicationRef.setNotFoundHandler(proxy); + applicationRef.setNotFoundHandler(proxy, this.config.getGlobalPrefix()); } public registerExceptionHandler() { @@ -105,7 +105,7 @@ export class RoutesResolver implements Resolver { ); const proxy = this.routerProxy.createExceptionLayerProxy(callback, handler); const applicationRef = this.container.getHttpAdapterRef(); - applicationRef.setErrorHandler && applicationRef.setErrorHandler(proxy); + applicationRef.setErrorHandler && applicationRef.setErrorHandler(proxy, this.config.getGlobalPrefix()); } public mapExternalException(err: any) { diff --git a/packages/core/test/utils/noop-adapter.spec.ts b/packages/core/test/utils/noop-adapter.spec.ts index 96ba9d4f4ab..8097861b65b 100644 --- a/packages/core/test/utils/noop-adapter.spec.ts +++ b/packages/core/test/utils/noop-adapter.spec.ts @@ -15,11 +15,11 @@ export class NoopHttpAdapter extends AbstractHttpAdapter { status(response: any, statusCode: number): any {} render(response: any, view: string, options: any): any {} redirect(response: any, statusCode: number, url: string) {} - setErrorHandler(handler: Function): any {} - setNotFoundHandler(handler: Function): any {} + setErrorHandler(handler: Function, prefix: string = '/'): any {} + setNotFoundHandler(handler: Function, prefix: string = '/'): 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 e3b5405cad0..b23f2fa4dcc 100644 --- a/packages/platform-express/adapters/express-adapter.ts +++ b/packages/platform-express/adapters/express-adapter.ts @@ -40,12 +40,12 @@ export class ExpressAdapter extends AbstractHttpAdapter { return response.redirect(statusCode, url); } - public setErrorHandler(handler: Function) { - return this.use(handler); + public setErrorHandler(handler: Function, prefix: string = '/') { + return this.use(prefix, handler); } - public setNotFoundHandler(handler: Function) { - return this.use(handler); + public setNotFoundHandler(handler: Function, prefix: string = '/') { + return this.use(prefix, handler); } public setHeader(response: any, name: string, value: string) { @@ -101,8 +101,8 @@ export class ExpressAdapter extends AbstractHttpAdapter { return request.url; } - public enableCors(options: CorsOptions) { - this.use(cors(options)); + public enableCors(options: CorsOptions, prefix: string = '/') { + return this.use(prefix, cors(options)); } public createMiddlewareFactory( @@ -125,14 +125,14 @@ 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 => this.use(prefix, parserMiddleware[parserKey])); } public getType(): string { diff --git a/packages/platform-fastify/adapters/fastify-adapter.ts b/packages/platform-fastify/adapters/fastify-adapter.ts index 8fc3c5b283f..27486fc9372 100644 --- a/packages/platform-fastify/adapters/fastify-adapter.ts +++ b/packages/platform-fastify/adapters/fastify-adapter.ts @@ -66,12 +66,32 @@ export class FastifyAdapter extends AbstractHttpAdapter { return response.status(code).redirect(url); } - public setErrorHandler(handler: Function) { - return this.instance.setErrorHandler(handler); + public setErrorHandler( + handler: Parameters[0], + prefix: string = '/', + ) { + return this.registerWithPrefix( + async ( + instance: fastify.FastifyInstance, + ): Promise => { + instance.setErrorHandler(handler); + }, + prefix, + ); } - public setNotFoundHandler(handler: Function) { - return this.instance.setNotFoundHandler(handler); + public setNotFoundHandler( + handler: Parameters[0], + prefix: string = '/', + ) { + return this.registerWithPrefix( + async ( + instance: fastify.FastifyInstance, + ): Promise => { + instance.setNotFoundHandler(handler); + }, + prefix, + ); } public getHttpServer(): T { @@ -132,12 +152,26 @@ export class FastifyAdapter extends AbstractHttpAdapter { return request.raw.url; } - public enableCors(options: CorsOptions) { - this.register(cors, options); + public enableCors(options: CorsOptions, prefix: string = '/') { + return this.registerWithPrefix( + async ( + instance: fastify.FastifyInstance, + ): Promise => { + instance.register(cors, (options as unknown) as {}); + }, + prefix, + ); } - public registerParserMiddleware() { - this.register(formBody); + public registerParserMiddleware(prefix: string = '/') { + return this.registerWithPrefix( + async ( + instance: fastify.FastifyInstance, + ): Promise => { + instance.register(formBody); + }, + prefix, + ); } public createMiddlewareFactory( @@ -171,4 +205,11 @@ export class FastifyAdapter extends AbstractHttpAdapter { public getType(): string { return 'fastify'; } + + protected registerWithPrefix>( + factory: T, + prefix: string = '/', + ): ReturnType { + return this.instance.register(factory, { prefix }); + } }