From 7a97cf1d769acb0b7cbcba651aea49137310aa76 Mon Sep 17 00:00:00 2001 From: Richard Zampieri Date: Tue, 3 Dec 2024 01:00:55 -0800 Subject: [PATCH] refactor: server listen accept 0 to set random port --- package.json | 2 +- .../listen.early.spec.ts | 15 ++++---- src/adapter-express/application-express.ts | 36 +++++++++++++------ .../micro-api/application-express-micro.ts | 19 ++++++++-- 4 files changed, 48 insertions(+), 24 deletions(-) diff --git a/package.json b/package.json index 6d310d6..c5ea317 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@expressots/adapter-express", - "version": "3.0.0-beta.4", + "version": "3.0.0-beta.4.1", "description": "Expressots - modern, fast, lightweight nodejs web framework (@adapter-express)", "author": "", "main": "./lib/cjs/index.js", diff --git a/src/adapter-express/application-express.early.spec/listen.early.spec.ts b/src/adapter-express/application-express.early.spec/listen.early.spec.ts index 7c83d0c..ae2eb51 100644 --- a/src/adapter-express/application-express.early.spec/listen.early.spec.ts +++ b/src/adapter-express/application-express.early.spec/listen.early.spec.ts @@ -18,8 +18,12 @@ jest.mock("../express-utils/inversify-express-server", () => { build: jest.fn().mockReturnValue({ set: jest.fn(), listen: jest.fn().mockImplementation((port, callback) => { + const server = { + on: jest.fn(), + close: jest.fn(), + }; callback(); - return { close: jest.fn() }; + return server; }), }), })), @@ -77,7 +81,7 @@ describe("AppExpress.listen() method", () => { set: jest.fn(), listen: jest.fn().mockImplementation((port, callback) => { callback(); - return { close: jest.fn() }; + return { on: jest.fn(), close: jest.fn() }; }), } as unknown as express.Application; @@ -97,7 +101,6 @@ describe("AppExpress.listen() method", () => { expect(mockApp.set).toHaveBeenCalledWith("env", "development"); expect(mockApp.listen).toHaveBeenCalledWith(port, expect.any(Function)); - expect(mockConsole.messageServer).toHaveBeenCalledWith(port, "development", undefined); }); it("should set the environment to development by default", async () => { @@ -115,12 +118,6 @@ describe("AppExpress.listen() method", () => { expect(mockApp.listen).toHaveBeenCalledWith(3000, expect.any(Function)); }); - it("should handle invalid port by defaulting to 3000", async () => { - await appExpress.listen(undefined as any); - - expect(mockApp.listen).toHaveBeenCalledWith(3000, expect.any(Function)); - }); - it("should handle process signals for graceful shutdown", async () => { await appExpress.listen(3000); diff --git a/src/adapter-express/application-express.ts b/src/adapter-express/application-express.ts index 8a3fc05..730b5ca 100644 --- a/src/adapter-express/application-express.ts +++ b/src/adapter-express/application-express.ts @@ -20,6 +20,7 @@ import { ExpressHandler, MiddlewareConfig } from "./application-express.types"; import { HttpStatusCodeMiddleware } from "./express-utils/http-status-middleware"; import { InversifyExpressServer } from "./express-utils/inversify-express-server"; import { setEngineEjs, setEngineHandlebars, setEnginePug } from "./render/engine"; +import { AddressInfo } from "net"; /** * The AppExpress class provides methods for configuring and running an Express application. @@ -240,20 +241,33 @@ export class AppExpress implements Server.IWebServer { this.environment = this.environment || "development"; this.app.set("env", this.environment); - this.port = typeof port === "string" ? parseInt(port, 10) : port || 3000; - this.serverInstance = this.app.listen(this.port, () => { - this.console.messageServer(this.port, this.environment, appInfo); + this.port = typeof port === "string" ? parseInt(port, 10) : port; - (["SIGTERM", "SIGHUP", "SIGBREAK", "SIGQUIT", "SIGINT"] as Array).forEach( - (signal) => { - process.on(signal, this.handleExit.bind(this)); - }, - ); - }); + return new Promise((resolve, reject) => { + this.serverInstance = this.app.listen(this.port, async () => { + this.port = (this.serverInstance?.address() as AddressInfo)?.port; + + this.console.messageServer(this.port, this.environment, appInfo); - await this.postServerInitialization(); + (["SIGTERM", "SIGHUP", "SIGBREAK", "SIGQUIT", "SIGINT"] as Array).forEach( + (signal) => { + process.on(signal, this.handleExit.bind(this)); + }, + ); - return this as IWebServerPublic; + try { + await this.postServerInitialization(); + resolve(this as IWebServerPublic); + } catch (error) { + this.logger.error(`Error during post-server initialization: ${error}`, "adapter-express"); + reject(error); + } + }); + this.serverInstance?.on("error", (error) => { + this.logger.error(`Error starting server: ${error.message}`, "adapter-express"); + reject(error); + }); + }); } /** diff --git a/src/adapter-express/micro-api/application-express-micro.ts b/src/adapter-express/micro-api/application-express-micro.ts index 0cac4bb..8a8ab17 100644 --- a/src/adapter-express/micro-api/application-express-micro.ts +++ b/src/adapter-express/micro-api/application-express-micro.ts @@ -236,7 +236,7 @@ class AppExpressMicro { */ public async listen(port: number | string, appInfo?: IConsoleMessage): Promise { const logger: Logger = new Logger(); - this.port = typeof port === "string" ? parseInt(port, 10) : port || 3000; + const normalizedPort = typeof port === "string" ? parseInt(port, 10) : port; this.configureMiddleware(); @@ -246,8 +246,16 @@ class AppExpressMicro { this.app.use(this.Middleware.getErrorHandler() as express.ErrorRequestHandler); } - return new Promise((resolve) => { - this.app.listen(this.port, () => { + return new Promise((resolve, reject) => { + const server = this.app.listen(normalizedPort, () => { + const address = server.address(); + + if (typeof address === "object" && address?.port) { + this.port = address.port; + } else { + this.port = normalizedPort; + } + const appInfoNormalized = appInfo ? `${appInfo?.appName} - ${appInfo?.appVersion} ` : ""; logger.info(`${appInfoNormalized}[${this.port}:${this.environment}]`, "MicroAPI"); @@ -258,6 +266,11 @@ class AppExpressMicro { ); resolve(); }); + + server.on("error", (error) => { + logger.error(`Error starting server: ${error.message}`, "MicroAPI"); + reject(error); + }); }); } }