Added control mode & more options
This commit is contained in:
parent
4d87ce2b83
commit
a6a8232a7a
23
config.json
23
config.json
|
@ -1,12 +1,23 @@
|
|||
{
|
||||
"listen": "http.sock",
|
||||
"socket": true,
|
||||
"listen": "8080",
|
||||
"socket": false,
|
||||
"map": [
|
||||
{
|
||||
"hosts": ["test.example.com"],
|
||||
"command": "node ../http/server.js",
|
||||
"file": "http-server.sock",
|
||||
"timeout": 60
|
||||
"hosts": ["docker-service.local"],
|
||||
"type": "control",
|
||||
"start_cmd": "docker-compose -f /path/to/docker-compose.yml start",
|
||||
"stop_cmd": "docker-compose -f /path/to/docker-compose.yml stop",
|
||||
"socket": false,
|
||||
"listen": "http://127.0.0.1:3000",
|
||||
"timeout": 120
|
||||
},
|
||||
{
|
||||
"hosts": ["node-webserver.local"],
|
||||
"type": "process",
|
||||
"start_cmd": "PORT=listen.sock node ./server/index.js",
|
||||
"socket": true,
|
||||
"listen": "listen.sock",
|
||||
"timeout": 120
|
||||
}
|
||||
]
|
||||
}
|
108
index.ts
108
index.ts
|
@ -3,6 +3,7 @@ import httpProxy from 'http-proxy';
|
|||
import fs from "fs";
|
||||
import child_process, { ChildProcess } from "child_process";
|
||||
import kill from "tree-kill";
|
||||
import axios from "axios";
|
||||
|
||||
let config = require("./config.json");
|
||||
|
||||
|
@ -27,17 +28,20 @@ class ServiceManager {
|
|||
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: {
|
||||
if(this.services[i].socket) {
|
||||
this.proxy.web(req, res, {target: {
|
||||
socketPath: await this.services[i].start(),
|
||||
host: "localhost"
|
||||
}
|
||||
});
|
||||
}});
|
||||
} else {
|
||||
this.proxy.web(req, res, {target: await this.services[i].start()});
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
console.warn("Host", host, "not found!");
|
||||
res.end("Host not found in host map");
|
||||
res.statusCode = 404;
|
||||
res.end();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -45,69 +49,111 @@ class ServiceManager {
|
|||
class Service implements ServiceConfig {
|
||||
|
||||
hosts: string[];
|
||||
command: string;
|
||||
file: string;
|
||||
|
||||
type: ServiceType;
|
||||
start_cmd: string;
|
||||
stop_cmd: string;
|
||||
|
||||
listen: string;
|
||||
socket: boolean;
|
||||
|
||||
waiting: boolean = false;
|
||||
waiting_clients: Function[] = [];
|
||||
|
||||
child?: ChildProcess;
|
||||
timeout?: number;
|
||||
life_check?: NodeJS.Timeout;
|
||||
timeout_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;
|
||||
|
||||
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;
|
||||
|
||||
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(this.timeout && !this.waiting) {
|
||||
if(this.timeout_check) clearTimeout(this.timeout_check);
|
||||
this.timeout_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);
|
||||
if(this.socket) if(fs.existsSync(this.listen)) fs.rmSync(this.listen);
|
||||
this.child = child_process.exec(this.start_cmd);
|
||||
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);
|
||||
});
|
||||
await this.life_check();
|
||||
this.alive = true;
|
||||
}
|
||||
|
||||
return this.file;
|
||||
return this.listen;
|
||||
}
|
||||
|
||||
async stop() {
|
||||
if(fs.existsSync(this.file)) fs.rmSync(this.file);
|
||||
if(this.socket) if(fs.existsSync(this.listen)) fs.rmSync(this.listen);
|
||||
if(this.alive) {
|
||||
this.alive = false;
|
||||
kill(this.child.pid, "SIGUSR2");
|
||||
if(this.type == "process") kill(this.child.pid, "SIGUSR2");
|
||||
if(this.type == "control") child_process.exec(this.stop_cmd);
|
||||
this.child = null;
|
||||
console.log(`Stopped service ${this.hosts[0]}`);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type ServiceType = "control" | "process";
|
||||
|
||||
interface ServiceConfig {
|
||||
hosts: string[];
|
||||
command: string;
|
||||
file: string;
|
||||
type: ServiceType;
|
||||
start_cmd: string;
|
||||
stop_cmd?: string;
|
||||
listen: string;
|
||||
socket: boolean;
|
||||
timeout?: number;
|
||||
};
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "^17.0.41",
|
||||
"axios": "^1.1.3",
|
||||
"http-proxy": "^1.18.1",
|
||||
"tree-kill": "^1.2.2"
|
||||
},
|
||||
|
|
113
pnpm-lock.yaml
Normal file
113
pnpm-lock.yaml
Normal file
|
@ -0,0 +1,113 @@
|
|||
lockfileVersion: 5.4
|
||||
|
||||
specifiers:
|
||||
'@types/http-proxy': ^1.17.9
|
||||
'@types/node': ^17.0.41
|
||||
axios: ^1.1.3
|
||||
http-proxy: ^1.18.1
|
||||
tree-kill: ^1.2.2
|
||||
|
||||
dependencies:
|
||||
'@types/node': 17.0.45
|
||||
axios: 1.1.3
|
||||
http-proxy: 1.18.1
|
||||
tree-kill: 1.2.2
|
||||
|
||||
devDependencies:
|
||||
'@types/http-proxy': 1.17.9
|
||||
|
||||
packages:
|
||||
|
||||
/@types/http-proxy/1.17.9:
|
||||
resolution: {integrity: sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw==}
|
||||
dependencies:
|
||||
'@types/node': 17.0.45
|
||||
dev: true
|
||||
|
||||
/@types/node/17.0.45:
|
||||
resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==}
|
||||
|
||||
/asynckit/0.4.0:
|
||||
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
||||
dev: false
|
||||
|
||||
/axios/1.1.3:
|
||||
resolution: {integrity: sha512-00tXVRwKx/FZr/IDVFt4C+f9FYairX517WoGCL6dpOntqLkZofjhu43F/Xl44UOpqa+9sLFDrG/XAnFsUYgkDA==}
|
||||
dependencies:
|
||||
follow-redirects: 1.15.2
|
||||
form-data: 4.0.0
|
||||
proxy-from-env: 1.1.0
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
dev: false
|
||||
|
||||
/combined-stream/1.0.8:
|
||||
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
dependencies:
|
||||
delayed-stream: 1.0.0
|
||||
dev: false
|
||||
|
||||
/delayed-stream/1.0.0:
|
||||
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
dev: false
|
||||
|
||||
/eventemitter3/4.0.7:
|
||||
resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
|
||||
dev: false
|
||||
|
||||
/follow-redirects/1.15.2:
|
||||
resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==}
|
||||
engines: {node: '>=4.0'}
|
||||
peerDependencies:
|
||||
debug: '*'
|
||||
peerDependenciesMeta:
|
||||
debug:
|
||||
optional: true
|
||||
dev: false
|
||||
|
||||
/form-data/4.0.0:
|
||||
resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
|
||||
engines: {node: '>= 6'}
|
||||
dependencies:
|
||||
asynckit: 0.4.0
|
||||
combined-stream: 1.0.8
|
||||
mime-types: 2.1.35
|
||||
dev: false
|
||||
|
||||
/http-proxy/1.18.1:
|
||||
resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
dependencies:
|
||||
eventemitter3: 4.0.7
|
||||
follow-redirects: 1.15.2
|
||||
requires-port: 1.0.0
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
dev: false
|
||||
|
||||
/mime-db/1.52.0:
|
||||
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/mime-types/2.1.35:
|
||||
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dependencies:
|
||||
mime-db: 1.52.0
|
||||
dev: false
|
||||
|
||||
/proxy-from-env/1.1.0:
|
||||
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
|
||||
dev: false
|
||||
|
||||
/requires-port/1.0.0:
|
||||
resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
|
||||
dev: false
|
||||
|
||||
/tree-kill/1.2.2:
|
||||
resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
|
||||
hasBin: true
|
||||
dev: false
|
Reference in a new issue