import http, { IncomingMessage, ServerResponse } from "http"; import httpProxy from 'http-proxy'; import fs from "fs"; import path from "path"; import os from "os"; import child_process, { ChildProcess } from "child_process"; import kill from "tree-kill"; class ServiceManager { pwd!: string; proxy = httpProxy.createProxy(); server = http.createServer((req, res) => this.listen(req, res)); services: Service[] = []; stopped: boolean = false; async setup() { this.pwd = fs.mkdtempSync(path.join(os.tmpdir(), "odp_")); this.server.listen(3000); } async stop() { if(this.stopped) return; this.stopped = true; this.services.forEach(s => s.stop()); fs.rmdirSync(this.pwd); } async listen(req: IncomingMessage, res: ServerResponse) { let oriHost = req.headers.host; let host = oriHost.indexOf(":") ? oriHost.split(':')[0] : oriHost; for(let i in this.services) { if(this.services[i].hosts.indexOf(host) !== -1) { this.proxy.web(req, res, { target: { socketPath: await this.services[i].start(), host: "localhost" } }); return; } } console.warn("Host", host, "not found!"); res.end("Host not found in host map"); } } class Service implements ServiceConfig { hosts: string[]; command: string; file: string; child?: ChildProcess; timeout?: number; life_check?: NodeJS.Timeout; last_activity: Date; man: ServiceManager; alive: boolean = false; constructor(man: ServiceManager, config: ServiceConfig) { this.man = man; this.hosts = config.hosts; this.command = config.command; this.file = config.file; this.timeout = config.timeout*1000; man.services.push(this); } async start() { this.last_activity = new Date; if(this.timeout) { if(this.life_check) clearTimeout(this.life_check); this.life_check = setTimeout(() => { if((new Date).getTime() > this.last_activity.getTime()+this.timeout) this.stop(); }, this.timeout); } if(!this.child) { if(fs.existsSync(this.file)) fs.rmSync(this.file); this.child = child_process.exec(this.command); console.log(`Started service ${this.hosts[0]}`); } if(!this.alive) { await new Promise((resolve) => { let int = setInterval(() => { if(fs.existsSync(this.file)) { clearInterval(int); resolve(null); } }, 100); }); this.alive = true; } return this.file; } async stop() { if(fs.existsSync(this.file)) fs.rmSync(this.file); if(this.alive) { this.alive = false; kill(this.child.pid, "SIGUSR2"); this.child = null; console.log(`Stopped service ${this.hosts[0]}`); } } } interface ServiceConfig { hosts: string[]; command: string; file: string; timeout?: number; }; let start = async (config: ServiceConfig[]) => { let man = new ServiceManager; await man.setup(); config.forEach(s => { new Service(man, s); }); let exit = async (e: any) => { if(e) console.log(e); man.stop(); process.exit(0); }; process.on('exit', exit); process.on('beforeExit', exit); process.on('SIGINT', exit); process.on('SIGUSR1', exit); process.on('SIGUSR2', exit); process.on('SIGTERM', exit); process.on('uncaughtException', console.error); }; let map = require("./map.json"); start(map);