2022-06-08 22:06:57 +02:00
|
|
|
import http, { IncomingMessage, ServerResponse } from "http";
|
|
|
|
import httpProxy from 'http-proxy';
|
|
|
|
import fs from "fs";
|
|
|
|
import child_process, { ChildProcess } from "child_process";
|
|
|
|
import kill from "tree-kill";
|
2022-11-11 08:02:17 +01:00
|
|
|
import axios from "axios";
|
2022-06-08 22:06:57 +02:00
|
|
|
|
2022-06-08 22:32:34 +02:00
|
|
|
let config = require("./config.json");
|
|
|
|
|
2022-06-08 22:06:57 +02:00
|
|
|
class ServiceManager {
|
|
|
|
|
|
|
|
proxy = httpProxy.createProxy();
|
|
|
|
server = http.createServer((req, res) => this.listen(req, res));
|
|
|
|
services: Service[] = [];
|
|
|
|
stopped: boolean = false;
|
|
|
|
|
|
|
|
async setup() {
|
2022-06-08 22:32:34 +02:00
|
|
|
this.server.listen(config.listen);
|
2022-06-08 22:06:57 +02:00
|
|
|
}
|
|
|
|
async stop() {
|
|
|
|
if(this.stopped) return;
|
|
|
|
this.stopped = true;
|
|
|
|
this.services.forEach(s => s.stop());
|
2022-06-08 22:32:34 +02:00
|
|
|
if(config.socket) fs.rmSync(config.listen);
|
2022-06-08 22:06:57 +02:00
|
|
|
}
|
|
|
|
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) {
|
2022-11-11 08:02:17 +01:00
|
|
|
if(this.services[i].socket) {
|
|
|
|
this.proxy.web(req, res, {target: {
|
2022-06-08 22:06:57 +02:00
|
|
|
socketPath: await this.services[i].start(),
|
|
|
|
host: "localhost"
|
2022-11-11 08:02:17 +01:00
|
|
|
}});
|
|
|
|
} else {
|
|
|
|
this.proxy.web(req, res, {target: await this.services[i].start()});
|
|
|
|
}
|
2022-06-08 22:06:57 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
console.warn("Host", host, "not found!");
|
2022-11-11 08:02:17 +01:00
|
|
|
res.statusCode = 404;
|
|
|
|
res.end();
|
2022-06-08 22:06:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
class Service implements ServiceConfig {
|
|
|
|
|
|
|
|
hosts: string[];
|
2022-11-11 08:02:17 +01:00
|
|
|
|
|
|
|
type: ServiceType;
|
|
|
|
start_cmd: string;
|
|
|
|
stop_cmd: string;
|
|
|
|
|
|
|
|
listen: string;
|
|
|
|
socket: boolean;
|
|
|
|
|
|
|
|
waiting: boolean = false;
|
|
|
|
waiting_clients: Function[] = [];
|
2022-06-08 22:06:57 +02:00
|
|
|
|
|
|
|
child?: ChildProcess;
|
|
|
|
timeout?: number;
|
2022-11-11 08:02:17 +01:00
|
|
|
timeout_check?: NodeJS.Timeout;
|
2022-06-08 22:06:57 +02:00
|
|
|
|
|
|
|
last_activity: Date;
|
|
|
|
man: ServiceManager;
|
|
|
|
alive: boolean = false;
|
|
|
|
|
|
|
|
constructor(man: ServiceManager, config: ServiceConfig) {
|
2022-11-11 08:02:17 +01:00
|
|
|
|
2022-06-08 22:06:57 +02:00
|
|
|
this.man = man;
|
2022-11-11 08:02:17 +01:00
|
|
|
|
2022-06-08 22:06:57 +02:00
|
|
|
this.hosts = config.hosts;
|
2022-11-11 08:02:17 +01:00
|
|
|
|
|
|
|
this.type = config.type;
|
|
|
|
this.start_cmd = config.start_cmd;
|
|
|
|
this.stop_cmd = config.stop_cmd;
|
|
|
|
|
|
|
|
this.socket = config.socket;
|
|
|
|
this.listen = config.listen;
|
|
|
|
|
|
|
|
this.timeout = config.timeout ? config.timeout*1000 : 0;
|
|
|
|
|
2022-06-08 22:06:57 +02:00
|
|
|
man.services.push(this);
|
2022-11-11 08:02:17 +01:00
|
|
|
|
2022-06-08 22:06:57 +02:00
|
|
|
}
|
2022-11-11 08:02:17 +01:00
|
|
|
|
2022-06-08 22:06:57 +02:00
|
|
|
async start() {
|
|
|
|
this.last_activity = new Date;
|
2022-11-11 08:02:17 +01:00
|
|
|
if(this.timeout && !this.waiting) {
|
|
|
|
if(this.timeout_check) clearTimeout(this.timeout_check);
|
|
|
|
this.timeout_check = setTimeout(() => {
|
2022-06-08 22:06:57 +02:00
|
|
|
if((new Date).getTime() > this.last_activity.getTime()+this.timeout) this.stop();
|
|
|
|
}, this.timeout);
|
|
|
|
}
|
|
|
|
if(!this.child) {
|
2022-11-11 08:02:17 +01:00
|
|
|
if(this.socket) if(fs.existsSync(this.listen)) fs.rmSync(this.listen);
|
|
|
|
this.child = child_process.exec(this.start_cmd);
|
2022-06-08 22:06:57 +02:00
|
|
|
console.log(`Started service ${this.hosts[0]}`);
|
|
|
|
}
|
|
|
|
if(!this.alive) {
|
2022-11-11 08:02:17 +01:00
|
|
|
await this.life_check();
|
2022-06-08 22:06:57 +02:00
|
|
|
this.alive = true;
|
|
|
|
}
|
2022-11-11 08:02:17 +01:00
|
|
|
return this.listen;
|
2022-06-08 22:06:57 +02:00
|
|
|
}
|
2022-11-11 08:02:17 +01:00
|
|
|
|
2022-06-08 22:06:57 +02:00
|
|
|
async stop() {
|
2022-11-11 08:02:17 +01:00
|
|
|
if(this.socket) if(fs.existsSync(this.listen)) fs.rmSync(this.listen);
|
2022-06-08 22:06:57 +02:00
|
|
|
if(this.alive) {
|
|
|
|
this.alive = false;
|
2022-11-11 08:02:17 +01:00
|
|
|
if(this.type == "process") kill(this.child.pid, "SIGUSR2");
|
|
|
|
if(this.type == "control") child_process.exec(this.stop_cmd);
|
2022-06-08 22:06:57 +02:00
|
|
|
this.child = null;
|
|
|
|
console.log(`Stopped service ${this.hosts[0]}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-11 08:02:17 +01:00
|
|
|
async life_check() {
|
|
|
|
if(!this.waiting) {
|
|
|
|
this.waiting = true;
|
|
|
|
let done = () => {
|
|
|
|
clearInterval(int);
|
|
|
|
this.waiting = false;
|
|
|
|
for(let i in this.waiting_clients) {
|
|
|
|
this.waiting_clients[i]();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
let int = setInterval(async () => {
|
|
|
|
if(this.socket) {
|
|
|
|
if(fs.existsSync(this.listen)) done();
|
|
|
|
} else {
|
|
|
|
try {
|
|
|
|
if([200, 301, 302].indexOf((await axios.get(this.listen)).status) !== -1) done();
|
|
|
|
} catch(e) {}
|
|
|
|
}
|
|
|
|
}, 100);
|
|
|
|
}
|
|
|
|
await new Promise((resolve) => {
|
|
|
|
this.waiting_clients.push(resolve);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-06-08 22:06:57 +02:00
|
|
|
}
|
|
|
|
|
2022-11-11 08:02:17 +01:00
|
|
|
type ServiceType = "control" | "process";
|
|
|
|
|
2022-06-08 22:06:57 +02:00
|
|
|
interface ServiceConfig {
|
|
|
|
hosts: string[];
|
2022-11-11 08:02:17 +01:00
|
|
|
type: ServiceType;
|
|
|
|
start_cmd: string;
|
|
|
|
stop_cmd?: string;
|
|
|
|
listen: string;
|
|
|
|
socket: boolean;
|
2022-06-08 22:06:57 +02:00
|
|
|
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);
|
|
|
|
|
|
|
|
};
|
|
|
|
|
2022-06-08 22:32:34 +02:00
|
|
|
start(config.map);
|