From 4a2bc7d80174720bb0ca97d339bec0335f20ca02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Celso=20In=C3=A1cio?= Date: Thu, 13 Apr 2023 21:59:38 -0300 Subject: [PATCH 1/3] implement way to execute sandbox in child_process --- lib/ForkSandbox.js | 47 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 lib/ForkSandbox.js diff --git a/lib/ForkSandbox.js b/lib/ForkSandbox.js new file mode 100644 index 0000000..f7d45b3 --- /dev/null +++ b/lib/ForkSandbox.js @@ -0,0 +1,47 @@ +const path = require('path'); +const child_process = require('child_process'); + +const Sandbox = require("./Sandbox"); + +const tasks = {}; +const childs = {}; + +const codeFileName = (namespace, codeId) => { + return `${namespace}/${codeId}.js`; +} + +const executeFunctionInSandbox = (id, data, callback) => { + + childs[id] = child_process.fork(path.resolve(__filename)); //require this file to execute process.on('message') below + + childs[id].send({ id, data }); + tasks[id] = callback; + + childs[id].on('message', (message) => { + const result = message.data; + tasks[message.id](result); + delete tasks[id]; + delete childs[id]; + }); +} + +module.exports = { + executeFunctionInSandbox, +} + +process.on('message', async (message) => { + try { + const { id, namespace, functionName } = message; + const { env, globalModules, asyncTimeout, syncTimeout, config, preCode, req, options } = message.data; + const sandbox = new Sandbox({ env, globalModules, asyncTimeout, syncTimeout, config }); + const filename = codeFileName(namespace, functionName); + + const script = sandbox.compileCode(filename, preCode.code) + const result = await sandbox.runScript(script, req, options); + process.send({ id, data: result }); + process.exit(0); + } catch (ex) { + process.send({ id: message.id, data: { error: ex.message } }); + process.exit(1); + } +}) From d7420c6b3dc1a492f34ef22e4e0b061e5ebc4529 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Celso=20In=C3=A1cio?= Date: Thu, 13 Apr 2023 22:08:08 -0300 Subject: [PATCH 2/3] Add child_process function execution to readme.md --- README.md | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/README.md b/README.md index c845d89..020597d 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ const myCode = mySandbox.compileCode('test.js', ` } `); + // express.Request compatible const req = { headers: {}, @@ -46,6 +47,48 @@ mySandbox.runScript(myCode, req).then(({status, body}) => { }); ``` +## Example of usage (with child_process) + +```javascript +const { executeFunctionInSandbox } = require('backstage-functions-sandbox/lib/ForkSandbox'); + +const myCode = ` + async function main(req, res) { + const result = req.body.x * req.body.y; + const name = Backstage.env.MY_VAR; + // you could call await here + return { name, result }; + } +`; + +const req = { + headers: {}, + query: {}, + body: { x: 10, y: 10} +}; + +executeFunctionInSandbox(taskId, { + env: { + MY_VAR: 'TRUE', // environment variable will be available on Backstage.env.MY_VAR + }, + globalModules: [ 'path' ], // put all available modules that will allow to import + asyncTimeout: 10000, + syncTimeout: 300, + preCode: code, // not required to compile using sandbox.Compilecode + req, + namespace: "foo", + functionName: "bar", + options, +}, (result) => { // when result callback was called, the child process of sandbox execution will be died + if(result.error) { + console.error({ error: err.message || err }) //print error return from function execution + } + + console.log(result) //print return from function execution +}) + +``` + ## Configuration | Name | Description | Example | From c0903757b78e913effa25ac6dbdbac1c56af2b5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Celso=20In=C3=A1cio?= Date: Fri, 14 Apr 2023 16:01:15 -0300 Subject: [PATCH 3/3] change callback into promise --- README.md | 16 +++++++++------- lib/ForkSandbox.js | 34 ++++++++++++++++++++++------------ 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 020597d..85e54cd 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,8 @@ const req = { body: { x: 10, y: 10} }; +const taskId = `${namespace}/${id}-${Date.now()}`; //or can be any uniq id + executeFunctionInSandbox(taskId, { env: { MY_VAR: 'TRUE', // environment variable will be available on Backstage.env.MY_VAR @@ -79,14 +81,14 @@ executeFunctionInSandbox(taskId, { namespace: "foo", functionName: "bar", options, -}, (result) => { // when result callback was called, the child process of sandbox execution will be died - if(result.error) { - console.error({ error: err.message || err }) //print error return from function execution - } - - console.log(result) //print return from function execution }) - +/* can return result using callback or using async await */ +.then(result => { + console.log(result) +}) +.catch(err => { + console.log(err) +}) ``` ## Configuration diff --git a/lib/ForkSandbox.js b/lib/ForkSandbox.js index f7d45b3..265d89d 100644 --- a/lib/ForkSandbox.js +++ b/lib/ForkSandbox.js @@ -3,25 +3,28 @@ const child_process = require('child_process'); const Sandbox = require("./Sandbox"); -const tasks = {}; const childs = {}; const codeFileName = (namespace, codeId) => { return `${namespace}/${codeId}.js`; } -const executeFunctionInSandbox = (id, data, callback) => { - - childs[id] = child_process.fork(path.resolve(__filename)); //require this file to execute process.on('message') below +const executeFunctionInSandbox = (id, data) => { + return new Promise((resolve, reject) => { + childs[id] = child_process.fork(path.resolve(__filename)); //require this file to execute process.on('message') below + + childs[id].send({ id, data }); - childs[id].send({ id, data }); - tasks[id] = callback; + childs[id].on('message', (message) => { + const result = message; + if (result.error) { + reject(result.error); + } + + resolve(result.data); + delete childs[id]; - childs[id].on('message', (message) => { - const result = message.data; - tasks[message.id](result); - delete tasks[id]; - delete childs[id]; + }); }); } @@ -36,8 +39,15 @@ process.on('message', async (message) => { const sandbox = new Sandbox({ env, globalModules, asyncTimeout, syncTimeout, config }); const filename = codeFileName(namespace, functionName); + const _req = Object.assign({ + method: null, + headers: {}, + body: {}, + query: {}, + }, req); + const script = sandbox.compileCode(filename, preCode.code) - const result = await sandbox.runScript(script, req, options); + const result = await sandbox.runScript(script, _req, options); process.send({ id, data: result }); process.exit(0); } catch (ex) {