import { createServer, RequestListener, Server as HttpServer } from "http"; import { createServer as createHttpsServer } from "https"; import { Router } from "./middleware/router.js"; import { routerDefaultLoggerFactory, defaultAppErrorHandler, HTML_HEADERS, STATUS } from "./common.js"; import { Request, Response, RouterOptions } from "./types.js"; import { ServerOptions } from "https"; import { Duplex } from "stream"; import { randomUUID } from "crypto"; import { Socket } from "net"; export interface AppOptions extends RouterOptions { onUpgrade?: (req: Request, socket: Duplex, head: Buffer) => Promise | void; serverOptions?: ServerOptions; https?: boolean; } export class App extends Router { public readonly listener: RequestListener; public readonly httpServer: HttpServer; private shouldCloseConnection: boolean = false; public connections: { [key: string]: Socket } = {}; constructor(config?: AppOptions) { super(config); if (config?.serverOptions) { this.serverOptions = { ...this.serverOptions, ...config.serverOptions }; } const errorHandler = defaultAppErrorHandler(); const loggerFactory = this.config && this.config.loggerFactory ? this.config.loggerFactory : routerDefaultLoggerFactory; this.listener = async (req: Request, res: Response) => { try { const logger = loggerFactory(req.uuid, req); try { req.logger = logger; if (this.config?.etag !== undefined) { res.setETag(this.config?.etag); } try { req.logger.trace(`request received for [%s]`, req.path); if (this.shouldCloseConnection) { res.setHeader("Connection", "close"); } await this.run(req, res); if (!res.headersSent) { await res.asyncEnd({ headers: HTML_HEADERS, status: STATUS.NOT_FOUND, body: "NOT FOUND" }); } } catch (e: any) { try { if (!res.headersSent) { await errorHandler(e, req, res); if (!res.headersSent) { req.logger.error(e); await res.asyncEnd({ headers: HTML_HEADERS, status: STATUS.ERROR, body: "SERVER ERROR" }); } } } catch (e3) { req.logger.error(e3); } } } catch (errorCtx) { logger.error(errorCtx); } } catch (loggerError) { console.error(loggerError); } }; const createS = (config && config.https ? createHttpsServer : createServer); this.httpServer = createS(this.serverOptions, this.listener); this.httpServer.on("connection", (conn) => { const key = randomUUID(); this.connections[key] = conn; conn.on('close', () => { delete this.connections[key]; }); }); if (config?.onUpgrade) { this.httpServer.on("upgrade", async (req, socket, head) => { try { const logger = loggerFactory(req.uuid, req); try { req.logger = logger; if (this.shouldCloseConnection) { socket.destroy(); } else { if (config?.onUpgrade) { const ret = await config.onUpgrade(req, socket, head); return ret; } else { socket.end("HTTP/1.1 400 Bad Request"); socket.destroy(); } } } catch (e) { logger.error(e); socket.end("HTTP/1.1 400 Bad Request"); socket.destroy(); } } catch (loggerError) { console.error(loggerError); socket.end("HTTP/1.1 400 Bad Request"); socket.destroy(); } }); } } public listenSync(...args: any[]): HttpServer { this.shouldCloseConnection = false; return this.httpServer.listen(...args) } public async listen(...args: any[]): Promise> { return new Promise((resolve, reject) => { try { this.shouldCloseConnection = false; this.httpServer.listen(...args, () => { resolve(this.httpServer); }); } catch (e) { reject(e); } }); } public async close(): Promise> { return new Promise>((resolve, reject) => { try { this.shouldCloseConnection = true; this.httpServer.close((err) => { if (err) { reject(err); } else { resolve(this.httpServer); } }); const keys = Object.keys(this.connections); for (const key of keys) { this.connections[key].destroy(); } } catch (e) { reject(e); } }); } public closeSync(cb: (err: Error | undefined) => void): void { this.shouldCloseConnection = true; this.httpServer.close(cb); const keys = Object.keys(this.connections); for (const key of keys) { this.connections[key].destroy(); } } } export class Server extends App { }