140 lines
3.1 KiB
TypeScript
140 lines
3.1 KiB
TypeScript
|
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);
|