feat: enhance DatabaseManagerService and QueryExecuterService with timeout settings, add AxiosPlugin for HTTP requests, and update DatabasePlugin to use query method

This commit is contained in:
Boris D
2025-10-09 17:20:33 +03:00
parent 0631e99886
commit 5b30b876e5
7 changed files with 104 additions and 13 deletions

View File

@ -79,6 +79,8 @@ export class DatabaseManagerService extends DatabaseEncryptionService {
user: queryUser ? database.q_username : database.c_username, user: queryUser ? database.q_username : database.c_username,
password: this.decryptPassword(database.password), password: this.decryptPassword(database.password),
database: database.database, database: database.database,
idleTimeout: 150e3,
connectTimeout: 2e3,
}; };
} }

View File

@ -61,7 +61,7 @@ export class QueryExecuterService {
{ {
removeOnComplete: true, removeOnComplete: true,
removeOnFail: true, removeOnFail: true,
attempts: 3, attempts: 0,
} }
); );
@ -130,6 +130,8 @@ export class QueryExecuterService {
const vm = new Vm({ const vm = new Vm({
memoryLimit: 128, memoryLimit: 128,
timeLimit: BigInt(100e9),
cpuTimeLimit: BigInt(5e9),
modules: modules, modules: modules,
plugins: plugins, plugins: plugins,
}); });

View File

@ -0,0 +1,54 @@
import axios from "axios";
import { Plugin } from "../plugin.class";
export class AxiosPlugin extends Plugin {
constructor(name: string) {
super(name, ["get", "post", "put", "delete", "head", "options", "patch"]);
}
static init(name: string): AxiosPlugin {
return new AxiosPlugin(name);
}
private configure(config) {
if (config.timeout > 10e3) {
config.timeout = 10e3;
} else {
config.timeout = 5e3;
}
return config;
}
async get(url: string, config?: any): Promise<any> {
return await axios.get(url, this.configure(config));
}
async post(url: string, data?: any, config?: any): Promise<any> {
return await axios.post(url, data, this.configure(config));
}
async put(url: string, data?: any, config?: any): Promise<any> {
return await axios.put(url, data, this.configure(config));
}
async delete(url: string, config?: any): Promise<any> {
return await axios.delete(url, this.configure(config));
}
async head(url: string, config?: any): Promise<any> {
return await axios.head(url, this.configure(config));
}
async options(url: string, config?: any): Promise<any> {
return await axios.options(url, this.configure(config));
}
async patch(url: string, data?: any, config?: any): Promise<any> {
return await axios.patch(url, data, this.configure(config));
}
onFinish() {
// No resources to clean up
}
}

View File

@ -9,7 +9,7 @@ export class DatabasePlugin extends Plugin {
); );
} }
super(name, ["execute"]); super(name, ["query"]);
} }
static async init( static async init(
@ -20,6 +20,8 @@ export class DatabasePlugin extends Plugin {
user: string; user: string;
password: string; password: string;
database: string; database: string;
idleTimeout: number;
connectTimeout: number;
} }
): Promise<DatabasePlugin> { ): Promise<DatabasePlugin> {
const dbConnection = await mysql.createConnection({ const dbConnection = await mysql.createConnection({
@ -28,19 +30,18 @@ export class DatabasePlugin extends Plugin {
port: config.port, port: config.port,
password: config.password, password: config.password,
database: config.database, database: config.database,
idleTimeout: config.idleTimeout,
connectTimeout: config.connectTimeout,
enableKeepAlive: true, enableKeepAlive: true,
}); });
await dbConnection.query("SET SESSION MAX_EXECUTION_TIME=2000;");
return new DatabasePlugin(name, dbConnection); return new DatabasePlugin(name, dbConnection);
} }
async execute(query): Promise<any> { async query(query): Promise<any> {
const [rows, fields] = await this.dbConnection.query(query); return await this.dbConnection.query(query);
return {
rows: rows,
fields: fields ?? [],
};
} }
onFinish() { onFinish() {

View File

@ -10,15 +10,21 @@ export class Vm {
private jail: any; private jail: any;
private plugins: Plugin[]; private plugins: Plugin[];
private isolate: ivm.Isolate; private isolate: ivm.Isolate;
private timeLimit?: bigint;
private cpuTimeLimit?: bigint;
constructor(configs: { constructor(configs: {
memoryLimit: number; memoryLimit: number;
timeLimit?: bigint;
cpuTimeLimit?: bigint;
modules: VModule[]; modules: VModule[];
plugins: Plugin[]; plugins: Plugin[];
}) { }) {
this.memoryLimit = configs.memoryLimit; this.memoryLimit = configs.memoryLimit;
this.modules = configs.modules; this.modules = configs.modules;
this.plugins = configs.plugins; this.plugins = configs.plugins;
this.timeLimit = configs.timeLimit;
this.cpuTimeLimit = configs.cpuTimeLimit;
} }
async init(): Promise<Vm> { async init(): Promise<Vm> {
@ -111,11 +117,23 @@ export class Vm {
const compiledScript = await this.isolate.compileScript(scriptWithResult); const compiledScript = await this.isolate.compileScript(scriptWithResult);
const interval = setInterval(() => {
if (
this.isolate.cpuTime > this.cpuTimeLimit ||
this.isolate.wallTime > this.timeLimit
) {
this.isolate.dispose();
rejectPromise(new Error("Script execution timed out"));
}
}, 500);
compiledScript.run(this.context); compiledScript.run(this.context);
try { try {
return await resultPromise; return await resultPromise;
} finally { } finally {
clearInterval(interval);
this.onFinish(); this.onFinish();
} }
} }

View File

@ -2,6 +2,7 @@ import { QueryExecuterService } from "src/query/executer/query.executer.service"
import { DatabasePlugin } from "./plugins/database.plugin"; import { DatabasePlugin } from "./plugins/database.plugin";
import { Query } from "src/query/entities/query.entity"; import { Query } from "src/query/entities/query.entity";
import { QueryPlugin } from "./plugins/query.plugin"; import { QueryPlugin } from "./plugins/query.plugin";
import { AxiosPlugin } from "./plugins/axios.plugin";
export const registeredPlugins = { export const registeredPlugins = {
db: async (service: QueryExecuterService, query: Query) => { db: async (service: QueryExecuterService, query: Query) => {
@ -17,6 +18,9 @@ export const registeredPlugins = {
return DatabasePlugin.init("db", databaseConnection); return DatabasePlugin.init("db", databaseConnection);
}, },
axios: async () => {
return AxiosPlugin.init("axios");
},
query: async (service: QueryExecuterService, query: Query) => { query: async (service: QueryExecuterService, query: Query) => {
return QueryPlugin.init(query, service); return QueryPlugin.init(query, service);
}, },

View File

@ -3,6 +3,7 @@
import "module/squel"; import "module/squel";
import "plugin/db"; import "plugin/db";
import "plugin/axios";
function createSQL(id) { function createSQL(id) {
return squel.select().from("test").where("id = ?", id).toString(); return squel.select().from("test").where("id = ?", id).toString();
@ -11,13 +12,22 @@ function createSQL(id) {
async function main(input, headers) { async function main(input, headers) {
const sql = createSQL(input.id); const sql = createSQL(input.id);
await db.execute("START TRANSACTION"); await db.query("START TRANSACTION");
// log(await db.execute('insert into test (name) values ("Test")')); // log(await db.query('insert into test (name) values ("Test")'));
const res = await db.execute(sql); log(new Date().toISOString());
// const a = await axios.get("https://httpbin.dev/delay/10", {
// timeout: 50000000,
// });
log(res); // log(a);
const res = await db.query(`
SELECT SLEEP(10000);
`);
log(new Date().toISOString());
return { return {
response: { response: {