|
|
@@ -9,6 +9,9 @@ const formidable = require("formidable");
|
|
|
const express = require("express");
|
|
|
const multer = require("multer");
|
|
|
const cors = require("cors");
|
|
|
+const { param } = require("express/lib/router");
|
|
|
+const http_request = require('http'); // 用于发送请求
|
|
|
+const { generateKey } = require("crypto");
|
|
|
|
|
|
function travel(dir, callback) {
|
|
|
fs.readdirSync(dir).forEach((file) => {
|
|
|
@@ -76,6 +79,15 @@ if (!fs.existsSync(path.join(getDir(), "config.json"))) {
|
|
|
);
|
|
|
}
|
|
|
|
|
|
+let child_processes = {};
|
|
|
+let child_logs = {};
|
|
|
+
|
|
|
+let config = fs.readFileSync(
|
|
|
+ path.join(getDir(), `config.json`),
|
|
|
+ "utf8"
|
|
|
+);
|
|
|
+config = JSON.parse(config);
|
|
|
+
|
|
|
exports.getDir = getDir;
|
|
|
exports.getEasySpiderLocation = getEasySpiderLocation;
|
|
|
FileMimes = JSON.parse(
|
|
|
@@ -115,6 +127,77 @@ fileServer.listen(8075, () => {
|
|
|
console.log("Server listening on http://localhost:8075");
|
|
|
});
|
|
|
|
|
|
+
|
|
|
+/**
|
|
|
+ * Write single data and success header to a response and end the response.
|
|
|
+ * @param {Response} res default response object
|
|
|
+ * @param {any} data response data
|
|
|
+ * @param {number} statusCode response status code
|
|
|
+ * @param {string} contentType response content type
|
|
|
+ */
|
|
|
+function writeAndEnd(res, data, statusCode = 200, contentType = 'application/json') {
|
|
|
+ res.writeHead(statusCode, { 'Content-Type': contentType });
|
|
|
+ res.write(data);
|
|
|
+ res.end();
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * Write a success response with JSON content type.
|
|
|
+ * @param {Response} res default response object
|
|
|
+ * @param {any} data response data
|
|
|
+ * @param {string} successMessage success message(optional)
|
|
|
+ */
|
|
|
+function writeSuccess(res, data, successMessage = "") {
|
|
|
+ // Write a success response with JSON content type
|
|
|
+ writeAndEnd(res, JSON.stringify({ success: successMessage, status: true, ...data}), 200, 'application/json');
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * Write an error response with JSON content type.
|
|
|
+ * @param {Response} res default response object
|
|
|
+ * @param {number} errorCode error code
|
|
|
+ * @param {string} errorMessage error message(optional)
|
|
|
+ */
|
|
|
+function writeError(res, errorCode, errorMessage="Internal Server Error") {
|
|
|
+ // Write an error response with JSON content type
|
|
|
+ writeAndEnd(res, JSON.stringify({ error: errorMessage, status: false }), errorCode, 'application/json');
|
|
|
+}
|
|
|
+
|
|
|
+function generateUuid() {
|
|
|
+ var s = [];
|
|
|
+ var hexDigits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
|
+ for (var i = 0; i < 36; i++) {
|
|
|
+ s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1)
|
|
|
+ }
|
|
|
+ s[14] = "4"
|
|
|
+ s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1)
|
|
|
+ s[8] = s[13] = s[18] = s[23] = "-"
|
|
|
+ let uuid = s.join("")
|
|
|
+ return uuid
|
|
|
+}
|
|
|
+
|
|
|
+// When error occurs in the handler, it will be caught and logged, and a 500 response will be sent if headers have not been sent yet.
|
|
|
+// This is useful to prevent the server from crashing due to unhandled exceptions in the request handlers
|
|
|
+function safeHandler(handler, res) {
|
|
|
+ return (...args) => {
|
|
|
+ try {
|
|
|
+ handler(...args);
|
|
|
+ } catch (err) {
|
|
|
+ console.error("Error handling request:", err);
|
|
|
+ if (!res.headersSent) {
|
|
|
+ res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
|
+ if (process.env.NODE_ENV === 'development') {
|
|
|
+ res.end(`Internal Server Error: \n${err.stack}`);
|
|
|
+ } else {
|
|
|
+ res.end("Internal Server Error");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
exports.start = function (port = 8074) {
|
|
|
http
|
|
|
.createServer(function (req, res) {
|
|
|
@@ -143,9 +226,6 @@ exports.start = function (port = 8074) {
|
|
|
// console.log(data);
|
|
|
// res.end('File uploaded and read successfully.');
|
|
|
// });
|
|
|
- } else if (pathName.indexOf(".") < 0) {
|
|
|
- //如果没有后缀名, 则为后台请求
|
|
|
- res.writeHead(200, { "Content-Type": "application/json" });
|
|
|
}
|
|
|
// else if(pathName.indexOf("index.html") >= 0) {
|
|
|
// fs.readFile(path.join(__dirname,"src", pathName), async (err, data) => {
|
|
|
@@ -164,7 +244,7 @@ exports.start = function (port = 8074) {
|
|
|
// }
|
|
|
// })
|
|
|
// }
|
|
|
- else {
|
|
|
+ else if (pathName.indexOf(".") >= 0) {
|
|
|
//如果有后缀名, 则为前端请求
|
|
|
// console.log(path.join(__dirname,"src/taskGrid", pathName));
|
|
|
const filePath = safeJoin(safeBase, pathName);
|
|
|
@@ -200,7 +280,7 @@ exports.start = function (port = 8074) {
|
|
|
req.on("data", function (chunk) {
|
|
|
body += chunk;
|
|
|
});
|
|
|
- req.on("end", function () {
|
|
|
+ req.on("end", safeHandler(() => {
|
|
|
// 设置响应头部信息及编码
|
|
|
if (pathName == "/queryTasks") {
|
|
|
//查询所有服务信息,只包括id和服务名称
|
|
|
@@ -226,9 +306,12 @@ exports.start = function (port = 8074) {
|
|
|
}
|
|
|
});
|
|
|
output.sort(compare("mtime"));
|
|
|
+ // 只修改外层为 {} 的响应增加 status 字段,其他响应不变,否则就和之前不兼容了
|
|
|
+ res.writeHead(200, { "Content-Type": "application/json" });
|
|
|
res.write(JSON.stringify(output));
|
|
|
res.end();
|
|
|
} else if (pathName == "/queryOSVersion") {
|
|
|
+ res.writeHead(200, { "Content-Type": "application/json" });
|
|
|
res.write(
|
|
|
JSON.stringify({ version: process.platform, bit: process.arch })
|
|
|
);
|
|
|
@@ -252,6 +335,7 @@ exports.start = function (port = 8074) {
|
|
|
}
|
|
|
}
|
|
|
);
|
|
|
+ res.writeHead(200, { "Content-Type": "application/json" });
|
|
|
res.write(JSON.stringify(output));
|
|
|
res.end();
|
|
|
} else if (pathName == "/queryTask") {
|
|
|
@@ -263,15 +347,11 @@ exports.start = function (port = 8074) {
|
|
|
"utf8"
|
|
|
);
|
|
|
// parse JSON string to JSON object
|
|
|
+ res.writeHead(200, { "Content-Type": "application/json" });
|
|
|
res.write(data);
|
|
|
res.end();
|
|
|
} catch (error) {
|
|
|
- res.write(
|
|
|
- JSON.stringify({
|
|
|
- error: "Cannot find task based on specified task ID.",
|
|
|
- })
|
|
|
- );
|
|
|
- res.end();
|
|
|
+ writeError(res, 404, "Cannot find task based on specified task ID.");
|
|
|
}
|
|
|
} else if (pathName == "/queryExecutionInstance") {
|
|
|
let params = url.parse(req.url, true).query;
|
|
|
@@ -282,18 +362,14 @@ exports.start = function (port = 8074) {
|
|
|
"utf8"
|
|
|
);
|
|
|
// parse JSON string to JSON object
|
|
|
+ res.writeHead(200, { "Content-Type": "application/json" });
|
|
|
res.write(data);
|
|
|
res.end();
|
|
|
} catch (error) {
|
|
|
- res.write(
|
|
|
- JSON.stringify({
|
|
|
- error:
|
|
|
- "Cannot find execution instance based on specified execution ID.",
|
|
|
- })
|
|
|
- );
|
|
|
- res.end();
|
|
|
+ writeError(res, 404, "Cannot find execution instance based on specified execution ID.");
|
|
|
}
|
|
|
} else if (pathName == "/") {
|
|
|
+ res.writeHead(200, { "Content-Type": "text/plain" });
|
|
|
res.write("Hello World!", "utf8");
|
|
|
res.end();
|
|
|
} else if (pathName == "/deleteTask") {
|
|
|
@@ -317,17 +393,9 @@ exports.start = function (port = 8074) {
|
|
|
}
|
|
|
}
|
|
|
);
|
|
|
- res.write(
|
|
|
- JSON.stringify({ success: "Task has been deleted successfully." })
|
|
|
- );
|
|
|
- res.end();
|
|
|
+ writeSuccess(res, {}, "Task has been deleted successfully.");
|
|
|
} catch (error) {
|
|
|
- res.write(
|
|
|
- JSON.stringify({
|
|
|
- error: "Cannot find task based on specified task ID.",
|
|
|
- })
|
|
|
- );
|
|
|
- res.end();
|
|
|
+ writeError(res, 404, "Cannot find task based on specified task ID.")
|
|
|
}
|
|
|
} else if (pathName == "/manageTask") {
|
|
|
body = querystring.parse(body);
|
|
|
@@ -384,25 +452,40 @@ exports.start = function (port = 8074) {
|
|
|
data,
|
|
|
(err) => {}
|
|
|
);
|
|
|
-
|
|
|
+
|
|
|
+ res.writeHead(200, { "Content-Type": "text/plain" });
|
|
|
res.write(id.toString(), "utf8");
|
|
|
res.end();
|
|
|
} else if (pathName == "/invokeTask") {
|
|
|
body = querystring.parse(body);
|
|
|
- let data = JSON.parse(body.params);
|
|
|
+ let data;
|
|
|
+ if (body.params === undefined || body.params == "") {
|
|
|
+ data = {};
|
|
|
+ } else {
|
|
|
+ try{
|
|
|
+ data = JSON.parse(body.params);
|
|
|
+ } catch (error) {
|
|
|
+ console.error(error);
|
|
|
+ writeError(res, 400, "Fail to parse parameters from json string.");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
let id = body.id;
|
|
|
+ if (id === undefined || id == "") {
|
|
|
+ writeError(res, 400, "Task ID is required.");
|
|
|
+ return;
|
|
|
+ }
|
|
|
let task = fs.readFileSync(
|
|
|
path.join(getDir(), `tasks/${id}.json`),
|
|
|
"utf8"
|
|
|
);
|
|
|
task = JSON.parse(task);
|
|
|
- try {
|
|
|
- task["links"] = data["urlList_0"];
|
|
|
- if (task["links"] == undefined) {
|
|
|
- task["links"] = "about:blank";
|
|
|
+ // 允许不填写 urlList_0,此时采用任务中的默认值
|
|
|
+ if (data["urlList_0"] !== undefined && data["urlList_0"] != "") {
|
|
|
+ try {
|
|
|
+ task["links"] = data["urlList_0"];
|
|
|
+ } catch (error) {
|
|
|
}
|
|
|
- } catch (error) {
|
|
|
- task["links"] = "about:blank";
|
|
|
}
|
|
|
for (const [key, value] of Object.entries(data)) {
|
|
|
for (let i = 0; i < task["inputParameters"].length; i++) {
|
|
|
@@ -434,7 +517,9 @@ exports.start = function (port = 8074) {
|
|
|
file_names.push(parseInt(file.split(".")[0]));
|
|
|
}
|
|
|
console.log(file);
|
|
|
- } catch (error) {}
|
|
|
+ } catch (error) {
|
|
|
+ console.error(error);
|
|
|
+ }
|
|
|
}
|
|
|
);
|
|
|
let eid = 0;
|
|
|
@@ -452,6 +537,9 @@ exports.start = function (port = 8074) {
|
|
|
task,
|
|
|
(err) => {}
|
|
|
);
|
|
|
+ console.log(`Task ${id} has been generated to file ${path.join(getDir(), `execution_instances/${eid}.json`)}`);
|
|
|
+ // res.writeHead
|
|
|
+ res.writeHead(200, { "Content-Type": "text/plain" });
|
|
|
res.write(eid.toString(), "utf8");
|
|
|
res.end();
|
|
|
} else if (pathName == "/getConfig") {
|
|
|
@@ -464,6 +552,7 @@ exports.start = function (port = 8074) {
|
|
|
if(lang == undefined){
|
|
|
lang = "-";
|
|
|
}
|
|
|
+ res.writeHead(200, { "Content-Type": "application/json" });
|
|
|
res.write(JSON.stringify(config_file));
|
|
|
res.end();
|
|
|
} else if (pathName == "/setUserDataFolder") {
|
|
|
@@ -476,14 +565,334 @@ exports.start = function (port = 8074) {
|
|
|
config["user_data_folder"] = body["user_data_folder"];
|
|
|
config = JSON.stringify(config);
|
|
|
fs.writeFile(path.join(getDir(), `config.json`), config, (err) => {});
|
|
|
- res.write(
|
|
|
- JSON.stringify({
|
|
|
- success: "User data folder has been set successfully.",
|
|
|
- })
|
|
|
+ writeSuccess(res, {}, "User data folder has been set successfully.");
|
|
|
+ } else if (pathName == "/executeTask") {
|
|
|
+ body = querystring.parse(body);
|
|
|
+ if (body === undefined || body.id === undefined || body.id == "") {
|
|
|
+ writeError(res, 400, "Execution instance ID is required.");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ let timeout = 10;
|
|
|
+ if (body.timeout !== undefined && body.timeout != "") {
|
|
|
+ try{
|
|
|
+ timeout = parseInt(body.timeout);
|
|
|
+ } catch (error) {
|
|
|
+ writeError(res, 400, "Timeout must be a number.");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 1. Find executable path
|
|
|
+ let platform_dir = "";
|
|
|
+ let executable_name = "easyspider_executestage";
|
|
|
+
|
|
|
+ if (process.platform === "win32" && process.arch === "x64") {
|
|
|
+ platform_dir = "chrome_win64";
|
|
|
+ executable_name += ".exe";
|
|
|
+ } else if (process.platform === "win32" && process.arch === "ia32") {
|
|
|
+ platform_dir = "chrome_win32";
|
|
|
+ executable_name += ".exe";
|
|
|
+ } else if (process.platform === "linux") {
|
|
|
+ platform_dir = "chrome_linux64";
|
|
|
+ } else if (process.platform === "darwin") {
|
|
|
+ writeError(res, 400, "Executing from remote control is not supported on macOS.");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const dev_executable_path = path.join(__dirname, platform_dir, executable_name);
|
|
|
+ const packaged_executable_path = path.join(getEasySpiderLocation(), 'resources', 'app', platform_dir, executable_name);
|
|
|
+ let executable_path = "";
|
|
|
+
|
|
|
+ if (fs.existsSync(dev_executable_path)) {
|
|
|
+ executable_path = dev_executable_path;
|
|
|
+ console.log("Using development executable path:", executable_path);
|
|
|
+ } else if (fs.existsSync(packaged_executable_path)) {
|
|
|
+ executable_path = packaged_executable_path;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (executable_path === "") {
|
|
|
+ writeError(res, 500, "Could not find the executable for this platform.");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (body.use_user_data == "true" || body.use_user_data == "1") {
|
|
|
+ body.use_user_data = 1;
|
|
|
+ } else {
|
|
|
+ body.use_user_data = 0;
|
|
|
+ }
|
|
|
+ try{
|
|
|
+ body.id = JSON.parse(body.id);
|
|
|
+ } catch (error) {
|
|
|
+ writeError(res, 400, "Fail to parse execution instance ID from json string.");
|
|
|
+ }
|
|
|
+ if (Array.isArray(body.id)) {
|
|
|
+ console.log("Multiple execution instances detected.");
|
|
|
+ let not_found = [];
|
|
|
+ for (let i = 0; i < body.id.length; i++) {
|
|
|
+ try{
|
|
|
+ // 尝试读取一次任务文件
|
|
|
+ let eid = parseInt(body.id[i]);
|
|
|
+ let file = fs.readFileSync(
|
|
|
+ path.join(getDir(), `execution_instances/${eid}.json`),
|
|
|
+ "utf8"
|
|
|
+ );
|
|
|
+ let task = JSON.parse(file);
|
|
|
+ // 忽略逻辑删除的任务
|
|
|
+ if (task == undefined || task.id == -2) {
|
|
|
+ console.log(`${eid} not found.`)
|
|
|
+ not_found.push(eid);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ not_found.push(body.id[i]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (not_found.length > 0) {
|
|
|
+ writeError(res, 404, `Cannot find execution instances based on specified execution IDs: ${not_found.join(", ")}`);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ for (let i = 0; i < body.id.length; i++) {
|
|
|
+ if (child_processes[body.id[i]] != null) {
|
|
|
+ writeError(res, 400, `Execution instance ${body.id[i]} is already running. If you want to run it again, please stop the current execution instance first.`);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ try{
|
|
|
+ // 尝试读取一次任务文件
|
|
|
+ let eid = parseInt(body.id);
|
|
|
+ let file = fs.readFileSync(
|
|
|
+ path.join(getDir(), `execution_instances/${eid}.json`),
|
|
|
+ "utf8"
|
|
|
+ );
|
|
|
+ let task = JSON.parse(file);
|
|
|
+ // 忽略逻辑删除的任务
|
|
|
+ if (task == undefined || task.id == -2) {
|
|
|
+ writeError(res, 404, "Cannot find execution instance based on specified execution ID.");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ writeError(res, 404, "Cannot find execution instance based on specified execution ID.");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (child_processes[body.id] != null) {
|
|
|
+ writeError(res, 400, `Execution instance ${body.id} is already running. If you want to run it again, please stop the current execution instance first.`);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ let config;
|
|
|
+ try{
|
|
|
+ config = fs.readFileSync(
|
|
|
+ path.join(getDir(), `config.json`),
|
|
|
+ "utf8"
|
|
|
+ );
|
|
|
+ config = JSON.parse(config);
|
|
|
+ } catch (error) {
|
|
|
+ writeError(res, 500, "Fail to parse config.json.");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ let ids_string;
|
|
|
+ if (Array.isArray(body.id)) {
|
|
|
+ // 多个执行实例
|
|
|
+ ids_string = body.id.join(",");
|
|
|
+ for (let i = 0; i < body.id.length; i++) {
|
|
|
+ child_logs[body.id[i]] = ""; // 初始化日志
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ ids_string = body.id;
|
|
|
+ child_logs[body.id] = ""; // 初始化日志
|
|
|
+ }
|
|
|
+ console.log(`Executing task with IDs: ${ids_string}`);
|
|
|
+ let spawn = require("child_process").spawn;
|
|
|
+ let server_address = `${config.webserver_address}:${config.webserver_port}`;
|
|
|
+ let secret_key = generateUuid(); // 生成一个随机的密钥
|
|
|
+ let parameters = [
|
|
|
+ "--ids",
|
|
|
+ "[" + ids_string + "]",
|
|
|
+ "--server_address",
|
|
|
+ server_address,
|
|
|
+ "--user_data",
|
|
|
+ body.use_user_data.toString(),
|
|
|
+ "--remote_control",
|
|
|
+ "1",
|
|
|
+ "--remote_control_key",
|
|
|
+ secret_key,
|
|
|
+ ];
|
|
|
+ const child_process = spawn(
|
|
|
+ executable_path,
|
|
|
+ parameters,
|
|
|
+ { detached: false, env: { ...process.env, 'PYTHONUNBUFFERED': '1', 'PYTHONUTF8': '1'} } // 设置环境变量,强制 utf-8 输出
|
|
|
);
|
|
|
- res.end();
|
|
|
+ if (!child_process.pid) {
|
|
|
+ writeError(res, 500, "Failed to start the child process and get its PID.");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ console.log(`Started child process with PID: ${child_process.pid}`);
|
|
|
+
|
|
|
+ let ipc_port_captured = false;
|
|
|
+ child_process.stdout.on("data", (data) => {
|
|
|
+ const output = data.toString();
|
|
|
+ console.log(`[PID ${child_process.pid}] stdout: ${output}`);
|
|
|
+ if (Array.isArray(body.id)) {
|
|
|
+ for (let i = 0; i < body.id.length; i++) {
|
|
|
+ child_logs[body.id[i]] = (child_logs[body.id[i]] || "") + output;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ child_logs[body.id] = (child_logs[body.id] || "") + output;
|
|
|
+ }
|
|
|
+
|
|
|
+ const match = output.match(/IPC_SERVER_PORT:(\d+)/);
|
|
|
+ if (match && match[1]) {
|
|
|
+ const ipc_port = parseInt(match[1], 10);
|
|
|
+ console.log(`Captured IPC port ${ipc_port} for PID ${child_process.pid}`);
|
|
|
+
|
|
|
+ // 存储进程信息
|
|
|
+ const process_info = {
|
|
|
+ pid: child_process.pid,
|
|
|
+ ipc_port: ipc_port,
|
|
|
+ process: child_process,
|
|
|
+ key: secret_key,
|
|
|
+ };
|
|
|
+ if (Array.isArray(body.id)) {
|
|
|
+ for (let i = 0; i < body.id.length; i++) {
|
|
|
+ child_processes[body.id[i]] = process_info;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ child_processes[body.id] = process_info;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!ipc_port_captured) {
|
|
|
+ ipc_port_captured = true;
|
|
|
+ writeSuccess(res, {message: `Task execution started successfully for ID(s): ${ids_string}`});
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ child_process.stderr.on("data", (data) => {
|
|
|
+ console.error(`[PID ${child_process.pid}] stderr: ${data.toString()}`);
|
|
|
+ });
|
|
|
+
|
|
|
+ child_process.on('close', (code) => {
|
|
|
+ console.log(`Child process with PID ${child_process.pid} exited with code ${code}`);
|
|
|
+ // 清理记录
|
|
|
+ for (const id in child_processes) {
|
|
|
+ if (child_processes[id].pid === child_process.pid) {
|
|
|
+ delete child_processes[id];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 添加一个超时,以防Python脚本未能成功启动IPC服务器
|
|
|
+ setTimeout(() => {
|
|
|
+ if (!ipc_port_captured) {
|
|
|
+ writeError(res, 500, "Failed to get IPC port from child process within timeout.");
|
|
|
+ child_process.kill('SIGKILL'); // 强制杀死没有响应的进程
|
|
|
+ }
|
|
|
+ }, timeout * 1000); // 5秒超时
|
|
|
+ } else if (pathName == "/stopTask") {
|
|
|
+ body = querystring.parse(body);
|
|
|
+ if (!body.id) {
|
|
|
+ writeError(res, 400, "Execution instance ID is required to stop a task.");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const process_info = child_processes[body.id];
|
|
|
+ if (!process_info || !process_info.ipc_port) {
|
|
|
+ writeError(res, 404, `No running process found for execution instance ID: ${body.id}. It might have already finished.`);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const options = {
|
|
|
+ hostname: '127.0.0.1',
|
|
|
+ port: process_info.ipc_port,
|
|
|
+ path: '/shutdown',
|
|
|
+ method: 'GET',
|
|
|
+ headers: {
|
|
|
+ 'Authorization': process_info.key, // 使用之前生成的密钥进行身份验证
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ console.log(`Sending shutdown command to http://localhost:${process_info.ipc_port}/shutdown`);
|
|
|
+
|
|
|
+ const req = http_request.request(options, (api_res) => {
|
|
|
+ if (api_res.statusCode === 200) {
|
|
|
+ writeSuccess(res, { message: `Shutdown command sent successfully to task ID ${body.id}.` });
|
|
|
+ } else {
|
|
|
+ writeError(res, 500, `IPC server responded with status: ${api_res.statusCode}`);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ req.on('error', (e) => {
|
|
|
+ console.error(`Problem with request to IPC server: ${e.stack}`);
|
|
|
+ writeError(res, 500, "Failed to send command to the task process. It might have crashed.");
|
|
|
+ });
|
|
|
+
|
|
|
+ req.end();
|
|
|
+
|
|
|
+ } else if (pathName == "/getTaskLog") {
|
|
|
+ let params = url.parse(req.url, true).query;
|
|
|
+ if (params === undefined || params.id === undefined || params.id == "") {
|
|
|
+ writeError(res, 400, "Execution instance ID is required.");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ let id = params.id;
|
|
|
+ const process_info = child_processes[id];
|
|
|
+ if (process_info && process_info.ipc_port) {
|
|
|
+ // 进程正在运行,直接读取日志
|
|
|
+ writeSuccess(res, { log: child_logs[id] || "" });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // 进程没有运行,则读取日志文件
|
|
|
+ // 列出 Data/Task_${id} 目录下的所有文件
|
|
|
+ let logFileFolder = path.join(getDir(), `Data/Task_${id}`);
|
|
|
+ // 列出目录下的所有文件,返回名称为 *.log 的文件
|
|
|
+ let logFilePath = "";
|
|
|
+ let logFileName = "";
|
|
|
+ fs.readdir(logFileFolder, (err, files) => {
|
|
|
+ if (err) {
|
|
|
+ console.error(err);
|
|
|
+ writeError(res, 400, "Log file does not exist.");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // 查找以 .log 结尾的文件
|
|
|
+ files.forEach((file) => {
|
|
|
+ if (file.endsWith(".log")) {
|
|
|
+ let p = path.join(logFileFolder, file);
|
|
|
+ if (file > logFileName) {
|
|
|
+ // 取最新的日志文件
|
|
|
+ logFilePath = p;
|
|
|
+ logFileName = file;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ if (logFilePath === "") {
|
|
|
+ writeError(res, 404, "Log file not found.");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ fs.readFile(logFilePath, "utf8", (err, data) => {
|
|
|
+ if (err) {
|
|
|
+ console.error(err);
|
|
|
+ writeError(res, 500, "Failed to read log file.");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // 缓存日志
|
|
|
+ writeSuccess(res, { log: data });
|
|
|
+ });
|
|
|
+ })
|
|
|
+ } else if (pathName == "/getTaskStatus"){
|
|
|
+ let params = url.parse(req.url, true).query;
|
|
|
+ if (params === undefined || params.id === undefined || params.id == "") {
|
|
|
+ writeError(res, 400, "Execution instance ID is required.");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ let id = params.id;
|
|
|
+ const process_info = child_processes[id];
|
|
|
+ if (process_info && process_info.ipc_port) {
|
|
|
+ // 进程正在运行,直接读取日志
|
|
|
+ writeSuccess(res, { running: true });
|
|
|
+ } else {
|
|
|
+ writeSuccess(res, { running: false })
|
|
|
+ }
|
|
|
}
|
|
|
- });
|
|
|
+ }, res));
|
|
|
})
|
|
|
.listen(port);
|
|
|
console.log("Server has started.");
|