feat(core): change listen to reject on server bind failures

pr update
This commit is contained in:
branbranmuffin
2019-11-08 00:33:43 -07:00
parent 9e20b0a858
commit 4cc2d1874a
14 changed files with 231 additions and 34237 deletions

View File

@@ -0,0 +1,47 @@
import { ExpressAdapter } from '@nestjs/platform-express';
import { Test, TestingModule } from '@nestjs/testing';
import { expect } from 'chai';
import * as express from 'express';
import { AppModule } from '../src/app.module';
import { INestApplication } from '@nestjs/common';
describe('Listen (Express Application)', () => {
let testModule: TestingModule;
let app: INestApplication;
beforeEach(async () => {
testModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = testModule.createNestApplication(new ExpressAdapter(express()));
});
afterEach(async () => {
app.close();
});
it('should resolve with httpServer on success', async () => {
const response = await app.listen(3000);
expect(response).to.eql(app.getHttpServer());
});
it('should reject if the port is not available', async () => {
await app.listen(3000);
const secondApp = testModule.createNestApplication(
new ExpressAdapter(express()),
);
try {
await secondApp.listen(3000);
} catch (error) {
expect(error.code).to.equal('EADDRINUSE');
}
});
it('should reject if there is an invalid host', async () => {
try {
await app.listen(3000, '1');
} catch (error) {
expect(error.code).to.equal('EADDRNOTAVAIL');
}
});
});

View File

@@ -0,0 +1,46 @@
import { FastifyAdapter } from '@nestjs/platform-fastify';
import { Test, TestingModule } from '@nestjs/testing';
import { expect } from 'chai';
import { AppModule } from '../src/app.module';
import { INestApplication } from '@nestjs/common';
describe('Listen (Fastify Application)', () => {
let testModule: TestingModule;
let app: INestApplication;
beforeEach(async () => {
testModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = testModule.createNestApplication(new FastifyAdapter());
});
afterEach(async () => {
app.close();
});
it('should resolve with httpServer on success', async () => {
const response = await app.listen(3000);
expect(response).to.eql(app.getHttpServer());
});
it('should reject if the port is not available', async () => {
await app.listen(3000);
const secondApp = testModule.createNestApplication(new FastifyAdapter());
try {
await secondApp.listen(3000);
} catch (error) {
expect(error.code).to.equal('EADDRINUSE');
}
await secondApp.close();
});
it('should reject if there is an invalid host', async () => {
try {
await app.listen(3000, '1');
} catch (error) {
expect(error.code).to.equal('EADDRNOTAVAIL');
}
});
});

View File

@@ -0,0 +1,12 @@
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.sayHello();
}
}

View File

@@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

View File

@@ -0,0 +1,8 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
sayHello(): string {
return 'Hello World!';
}
}

View File

@@ -12,7 +12,7 @@ describe('ErrorGateway', () => {
providers: [ErrorGateway], providers: [ErrorGateway],
}).compile(); }).compile();
app = await testingModule.createNestApplication(); app = await testingModule.createNestApplication();
await app.listenAsync(3000); await app.listen(3000);
}); });
it(`should handle error`, async () => { it(`should handle error`, async () => {

View File

@@ -17,7 +17,7 @@ describe('WebSocketGateway (ack)', () => {
it(`should handle message with ack (http)`, async () => { it(`should handle message with ack (http)`, async () => {
app = await createNestApp(AckGateway); app = await createNestApp(AckGateway);
await app.listenAsync(3000); await app.listen(3000);
ws = io.connect('http://localhost:8080'); ws = io.connect('http://localhost:8080');
await new Promise<void>(resolve => await new Promise<void>(resolve =>
@@ -30,7 +30,7 @@ describe('WebSocketGateway (ack)', () => {
it(`should handle message with ack & without data (http)`, async () => { it(`should handle message with ack & without data (http)`, async () => {
app = await createNestApp(AckGateway); app = await createNestApp(AckGateway);
await app.listenAsync(3000); await app.listen(3000);
ws = io.connect('http://localhost:8080'); ws = io.connect('http://localhost:8080');
await new Promise<void>(resolve => await new Promise<void>(resolve =>

View File

@@ -19,7 +19,7 @@ describe('WebSocketGateway', () => {
it(`should handle message (2nd port)`, async () => { it(`should handle message (2nd port)`, async () => {
app = await createNestApp(ApplicationGateway); app = await createNestApp(ApplicationGateway);
await app.listenAsync(3000); await app.listen(3000);
ws = io.connect('http://localhost:8080'); ws = io.connect('http://localhost:8080');
ws.emit('push', { ws.emit('push', {
@@ -35,7 +35,7 @@ describe('WebSocketGateway', () => {
it(`should handle message (http)`, async () => { it(`should handle message (http)`, async () => {
app = await createNestApp(ServerGateway); app = await createNestApp(ServerGateway);
await app.listenAsync(3000); await app.listen(3000);
ws = io.connect('http://localhost:3000'); ws = io.connect('http://localhost:3000');
ws.emit('push', { ws.emit('push', {
@@ -51,7 +51,7 @@ describe('WebSocketGateway', () => {
it(`should handle message (2 gateways)`, async () => { it(`should handle message (2 gateways)`, async () => {
app = await createNestApp(ApplicationGateway, NamespaceGateway); app = await createNestApp(ApplicationGateway, NamespaceGateway);
await app.listenAsync(3000); await app.listen(3000);
ws = io.connect('http://localhost:8080'); ws = io.connect('http://localhost:8080');
io.connect('http://localhost:8080/test').emit('push', {}); io.connect('http://localhost:8080/test').emit('push', {});

View File

@@ -21,7 +21,7 @@ describe('WebSocketGateway (WsAdapter)', () => {
it(`should handle message (2nd port)`, async () => { it(`should handle message (2nd port)`, async () => {
app = await createNestApp(ApplicationGateway); app = await createNestApp(ApplicationGateway);
await app.listenAsync(3000); await app.listen(3000);
ws = new WebSocket('ws://localhost:8080'); ws = new WebSocket('ws://localhost:8080');
await new Promise(resolve => ws.on('open', resolve)); await new Promise(resolve => ws.on('open', resolve));
@@ -44,7 +44,7 @@ describe('WebSocketGateway (WsAdapter)', () => {
it(`should handle message (http)`, async () => { it(`should handle message (http)`, async () => {
app = await createNestApp(ServerGateway); app = await createNestApp(ServerGateway);
await app.listenAsync(3000); await app.listen(3000);
ws = new WebSocket('ws://localhost:3000'); ws = new WebSocket('ws://localhost:3000');
await new Promise(resolve => ws.on('open', resolve)); await new Promise(resolve => ws.on('open', resolve));
@@ -69,7 +69,7 @@ describe('WebSocketGateway (WsAdapter)', () => {
this.retries(10); this.retries(10);
app = await createNestApp(ApplicationGateway, CoreGateway); app = await createNestApp(ApplicationGateway, CoreGateway);
await app.listenAsync(3000); await app.listen(3000);
// open websockets delay // open websockets delay
await new Promise(resolve => setTimeout(resolve, 1000)); await new Promise(resolve => setTimeout(resolve, 1000));

34237
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -54,15 +54,6 @@ export interface INestApplication extends INestApplicationContext {
*/ */
getUrl(): Promise<string>; getUrl(): Promise<string>;
/**
* Starts the application (can be awaited).
*
* @param {number|string} port
* @param {string} [hostname]
* @returns {Promise}
*/
listenAsync(port: number | string, hostname?: string): Promise<any>;
/** /**
* Registers a prefix for every HTTP route path. * Registers a prefix for every HTTP route path.
* *

View File

@@ -228,25 +228,26 @@ export class NestApplication
this.httpAdapter.enableCors(options); this.httpAdapter.enableCors(options);
} }
public async listen( public async listen(port: number | string): Promise<any>;
port: number | string, public async listen(port: number | string, hostname: string): Promise<any>;
callback?: () => void,
): Promise<any>;
public async listen(
port: number | string,
hostname: string,
callback?: () => void,
): Promise<any>;
public async listen(port: number | string, ...args: any[]): Promise<any> { public async listen(port: number | string, ...args: any[]): Promise<any> {
!this.isInitialized && (await this.init()); !this.isInitialized && (await this.init());
this.isListening = true;
this.httpAdapter.listen(port, ...args);
return this.httpServer;
}
public listenAsync(port: number | string, hostname?: string): Promise<any> { return new Promise((resolve, reject) => {
return new Promise(resolve => { const errorHandler = e => {
const server: any = this.listen(port, hostname, () => resolve(server)); this.logger.error(e.toString());
reject(e);
};
this.httpServer.once('error', errorHandler);
this.httpAdapter.listen(port, ...args, () => {
const address = this.httpServer.address();
if (address) {
this.httpServer.removeListener('error', errorHandler);
this.isListening = true;
resolve(this.httpServer);
}
});
}); });
} }
@@ -256,30 +257,33 @@ export class NestApplication
this.logger.error(MESSAGES.CALL_LISTEN_FIRST); this.logger.error(MESSAGES.CALL_LISTEN_FIRST);
reject(MESSAGES.CALL_LISTEN_FIRST); reject(MESSAGES.CALL_LISTEN_FIRST);
} }
this.httpServer.on('listening', () => { const address = this.httpServer.address();
const address = this.httpServer.address(); resolve(this.formatAddress(address));
if (typeof address === 'string') {
if (platform() === 'win32') {
return address;
}
const basePath = encodeURIComponent(address);
return `${this.getProtocol()}+unix://${basePath}`;
}
let host = this.host();
if (address && address.family === 'IPv6') {
if (host === '::') {
host = '[::1]';
} else {
host = `[${host}]`;
}
} else if (host === '0.0.0.0') {
host = '127.0.0.1';
}
resolve(`${this.getProtocol()}://${host}:${address.port}`);
});
}); });
} }
private formatAddress(address) {
if (typeof address === 'string') {
if (platform() === 'win32') {
return address;
}
const basePath = encodeURIComponent(address);
return `${this.getProtocol()}+unix://${basePath}`;
}
let host = this.host();
if (address && address.family === 'IPv6') {
if (host === '::') {
host = '[::1]';
} else {
host = `[${host}]`;
}
} else if (host === '0.0.0.0') {
host = '127.0.0.1';
}
return `${this.getProtocol()}://${host}:${address.port}`;
}
public setGlobalPrefix(prefix: string): this { public setGlobalPrefix(prefix: string): this {
this.config.setGlobalPrefix(prefix); this.config.setGlobalPrefix(prefix);
return this; return this;

View File

@@ -11,7 +11,7 @@ async function bootstrap() {
* transport: Transport.TCP, * transport: Transport.TCP,
* options: { retryAttempts: 5, retryDelay: 3000 }, * options: { retryAttempts: 5, retryDelay: 3000 },
* }); * });
* await app.listenAsync(); * await app.listen();
* *
*/ */
const app = await NestFactory.create(AppModule); const app = await NestFactory.create(AppModule);

View File

@@ -15,7 +15,7 @@ async function bootstrap() {
* protoPath: join(__dirname, './hero/hero.proto'), * protoPath: join(__dirname, './hero/hero.proto'),
* } * }
* }); * });
* await app.listenAsync(); * await app.listen();
* *
*/ */
const app = await NestFactory.create(AppModule); const app = await NestFactory.create(AppModule);