UNPKG

esa-cli

Version:

A CLI for operating Alibaba Cloud ESA EdgeRoutine (Edge Functions).

236 lines (235 loc) 9.74 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import * as http from 'http'; import spawn from 'cross-spawn'; import fetch from 'node-fetch'; import logger from '../../../libs/logger.js'; import { getRoot } from '../../../utils/fileUtils/base.js'; import { EW2BinPath } from '../../../utils/installEw2.js'; import { HttpProxyAgent } from 'http-proxy-agent'; import chalk from 'chalk'; import t from '../../../i18n/index.js'; import sleep from '../../../utils/sleep.js'; const getColorForStatusCode = (statusCode, message) => { if (statusCode >= 100 && statusCode < 200) { return chalk.blue(`${statusCode} ${message}`); } else if (statusCode >= 200 && statusCode < 300) { return chalk.green(`${statusCode} ${message}`); } else if (statusCode >= 300 && statusCode < 400) { return chalk.yellow(`${statusCode} ${message}`); } else if (statusCode >= 400 && statusCode < 500) { return chalk.red(`${statusCode} ${message}`); } else if (statusCode >= 500) { return chalk.magenta(chalk.bold(`${statusCode} ${message}`)); } else { return `${statusCode} ${message}`; } }; class Ew2Server { constructor(props) { this.worker = null; this.startingWorker = false; this.workerStartTimeout = undefined; this.server = null; this.restarting = false; this.port = 18080; // @ts-ignore if (global.port) this.port = global.port; if (props.port) this.port = props.port; if (props.onClose) this.onClose = props.onClose; } start() { return __awaiter(this, void 0, void 0, function* () { this.startingWorker = true; const result = yield this.openEdgeWorker(); if (!result) { throw new Error('Worker start failed'); } this.createServer(); }); } openEdgeWorker() { if (this.worker) { return Promise.resolve(); } const root = getRoot(); // @ts-ignore const id = global.id || ''; return new Promise((resolve, reject) => { var _a, _b, _c; this.worker = spawn(EW2BinPath, [ '--config_file', `${root}/.dev/config-${id}.toml`, '--log_stdout', '-v' ], { stdio: ['pipe', 'pipe', 'pipe'] }); this.workerStartTimeout = setTimeout(() => { reject(new Error(t('dev_worker_timeout').d('Worker start timeout'))); this.worker && this.worker.kill(); }, 60000); const sendToRuntime = () => { return new Promise((resolveStart) => { // @ts-ignore const ew2Port = global.ew2Port; const options = { hostname: '127.0.0.1', port: ew2Port, method: 'GET' }; const req = http.get(options, (res) => { resolveStart(res.statusCode); }); req.on('error', (err) => { resolveStart(null); }); req.end(); }); }; const checkRuntimeStart = () => __awaiter(this, void 0, void 0, function* () { while (this.startingWorker) { const [result] = yield Promise.all([sendToRuntime(), sleep(500)]); if (result) { this.startingWorker = false; this.clearTimeout(); resolve(result); } } }); checkRuntimeStart(); (_a = this.worker.stdout) === null || _a === void 0 ? void 0 : _a.setEncoding('utf8'); (_b = this.worker.stdout) === null || _b === void 0 ? void 0 : _b.on('data', this.stdoutHandler.bind(this)); (_c = this.worker.stderr) === null || _c === void 0 ? void 0 : _c.on('data', this.stderrHandler.bind(this)); this.worker.on('close', this.closeHandler.bind(this)); this.worker.on('error', this.errorHandler.bind(this)); process.on('SIGTERM', () => { this.worker && this.worker.kill(); }); }); } clearTimeout() { clearTimeout(this.workerStartTimeout); } createServer() { this.server = http.createServer((req, res) => __awaiter(this, void 0, void 0, function* () { try { const host = req.headers.host; const url = req.url; // @ts-ignore const ew2Port = global.ew2Port; // @ts-ignore const localUpstream = global.localUpstream; const workerRes = yield fetch(`http://${localUpstream ? localUpstream : host}${url}`, { method: 'GET', headers: { 'x-er-context': 'eyJzaXRlX2lkIjogIjYyMjcxODQ0NjgwNjA4IiwgInNpdGVfbmFtZSI6ICJjb21wdXRlbHguYWxpY2RuLXRlc3QuY29tIiwgInNpdGVfcmVjb3JkIjogIm1vY2hlbi1uY2RuLmNvbXB1dGVseC5hbGljZG4tdGVzdC5jb20iLCAiYWxpdWlkIjogIjEzMjI0OTI2ODY2NjU2MDgiLCAic2NoZW1lIjoiaHR0cCIsICAiaW1hZ2VfZW5hYmxlIjogdHJ1ZX0=', 'x-er-id': 'a.bA' }, agent: new HttpProxyAgent(`http://127.0.0.1:${ew2Port}`) }); const workerHeaders = Object.fromEntries(workerRes.headers.entries()); // 解决 gzip 兼容性问题,防止net::ERR_CONTENT_DECODING_FAILED workerHeaders['content-encoding'] = 'identity'; if (workerRes.body) { // if (workerRes.headers.get('content-type')?.includes('text/')) { // const text = await workerRes.text(); // // 出现换行符之类会导致 content-length 不一致 // workerHeaders['content-length'] = // Buffer.byteLength(text).toString(); // console.log(workerHeaders['content-length']); // res.writeHead(workerRes.status, workerHeaders); // res.end(text); // } else { // res.writeHead(workerRes.status, workerHeaders); // workerRes.body.pipe(res); // } res.writeHead(workerRes.status, workerHeaders); workerRes.body.pipe(res); logger.log(`[ESA Dev] ${req.method} ${url} ${getColorForStatusCode(workerRes.status, workerRes.statusText)}`); } else { res.writeHead(500, { 'Content-Type': 'text/plain' }); res.end('EW2 return null'); } } catch (err) { console.log(err); } })); this.server.listen(this.port, () => { logger.log(`listening on port ${this.port}`); }); } stdoutHandler(chunk) { logger.log(`${chalk.bgGreen('[Worker]')} ${chunk.toString().trim()}`); } stderrHandler(chunk) { logger.subError(`${chalk.bgGreen('[Worker Error]')} ${chunk.toString().trim()}`); } errorHandler(error) { logger.error(error.message ? error.message : error); if (error.code && error.code === 'EACCES') { logger.pathEacces(EW2BinPath); } this.stop(); } closeHandler(code, signal) { if (this.restarting) { this.restarting = false; return; } this.stop().then(() => { logger.log(t('dev_server_closed').d('Worker server closed')); logger.info('Worker server closed'); // @ts-ignore global.port = undefined; this.onClose && this.onClose(); }); } runCommand(command) { var _a, _b; (_b = (_a = this.worker) === null || _a === void 0 ? void 0 : _a.stdin) === null || _b === void 0 ? void 0 : _b.write(command); } stop() { return new Promise((resolve) => { var _a; if (!this.worker) { resolve(false); return; } const onExit = (code, signal) => { this.worker = null; resolve(true); }; this.worker.on('exit', onExit); this.worker.kill('SIGTERM'); (_a = this.server) === null || _a === void 0 ? void 0 : _a.close(); }); } restart() { return __awaiter(this, void 0, void 0, function* () { this.restarting = true; yield this.stop(); this.start(); logger.log(t('dev_server_restart').d('Worker server restarted')); logger.info('Worker server restarted'); }); } } export default Ew2Server;