### What problem does this PR solve? Improve usability of Node.js/JavaScript code executor. ### Type of change - [x] Refactoring --------- Co-authored-by: Kevin Hu <kevinhu.sh@gmail.com> Co-authored-by: writinwaters <93570324+writinwaters@users.noreply.github.com>tags/v0.20.0
| --- | --- | ||||
| ## Usage | |||||
| ### 🐍 A Python example | |||||
| ```python | |||||
| def main(arg1: str, arg2: str) -> str: | |||||
| return f"result: {arg1 + arg2}" | |||||
| ``` | |||||
| ### 🟨 JavaScript examples | |||||
| A simple sync function | |||||
| ```javascript | |||||
| function main({arg1, arg2}) { | |||||
| return arg1+arg2 | |||||
| } | |||||
| ``` | |||||
| Async funcion with aioxs | |||||
| ```javascript | |||||
| const axios = require('axios'); | |||||
| async function main() { | |||||
| try { | |||||
| const response = await axios.get('https://github.com/infiniflow/ragflow'); | |||||
| return 'Body:' + response.data; | |||||
| } catch (error) { | |||||
| return 'Error:' + error.message; | |||||
| } | |||||
| } | |||||
| ``` | |||||
| --- | |||||
| ## 📋 FAQ | ## 📋 FAQ | ||||
| ### ❓Sandbox Not Working? | ### ❓Sandbox Not Working? |
| # | # | ||||
| import base64 | import base64 | ||||
| from core.container import _CONTAINER_EXECUTION_SEMAPHORES | |||||
| from core.logger import logger | from core.logger import logger | ||||
| from fastapi import Request | from fastapi import Request | ||||
| from models.enums import ResultStatus | |||||
| from models.enums import ResultStatus, SupportLanguage | |||||
| from models.schemas import CodeExecutionRequest, CodeExecutionResult | from models.schemas import CodeExecutionRequest, CodeExecutionResult | ||||
| from services.execution import execute_code | from services.execution import execute_code | ||||
| from services.limiter import limiter | from services.limiter import limiter | ||||
| from services.security import analyze_code_security | from services.security import analyze_code_security | ||||
| from core.container import _CONTAINER_EXECUTION_SEMAPHORES | |||||
| async def healthz_handler(): | async def healthz_handler(): | ||||
| return {"status": "ok"} | return {"status": "ok"} | ||||
| @limiter.limit("5/second") | @limiter.limit("5/second") | ||||
| async def run_code_handler(req: CodeExecutionRequest, request: Request): | async def run_code_handler(req: CodeExecutionRequest, request: Request): | ||||
| logger.info("🟢 Received /run request") | logger.info("🟢 Received /run request") | ||||
| async with _CONTAINER_EXECUTION_SEMAPHORES[req.language]: | async with _CONTAINER_EXECUTION_SEMAPHORES[req.language]: | ||||
| code = base64.b64decode(req.code_b64).decode("utf-8") | code = base64.b64decode(req.code_b64).decode("utf-8") | ||||
| if req.language == SupportLanguage.NODEJS: | |||||
| code += "\n\nmodule.exports = { main };" | |||||
| req.code_b64 = base64.b64encode(code.encode("utf-8")).decode("utf-8") | |||||
| is_safe, issues = analyze_code_security(code, language=req.language) | is_safe, issues = analyze_code_security(code, language=req.language) | ||||
| if not is_safe: | if not is_safe: | ||||
| issue_details = "\n".join([f"Line {lineno}: {issue}" for issue, lineno in issues]) | issue_details = "\n".join([f"Line {lineno}: {issue}" for issue, lineno in issues]) |
| router = APIRouter() | router = APIRouter() | ||||
| router.get("/healthz")(healthz_handler) | router.get("/healthz")(healthz_handler) | ||||
| router.post("/run")(run_code_handler) | |||||
| router.post("/run")(run_code_handler) | |||||
| _CONTAINER_QUEUES: dict[SupportLanguage, Queue] = {} | _CONTAINER_QUEUES: dict[SupportLanguage, Queue] = {} | ||||
| _CONTAINER_LOCK: asyncio.Lock = asyncio.Lock() | _CONTAINER_LOCK: asyncio.Lock = asyncio.Lock() | ||||
| _CONTAINER_EXECUTION_SEMAPHORES:dict[SupportLanguage,asyncio.Semaphore] = {} | |||||
| _CONTAINER_EXECUTION_SEMAPHORES: dict[SupportLanguage, asyncio.Semaphore] = {} | |||||
| async def init_containers(size: int) -> tuple[int, int]: | async def init_containers(size: int) -> tuple[int, int]: | ||||
| _CONTAINER_QUEUES[SupportLanguage.PYTHON].get_nowait() | _CONTAINER_QUEUES[SupportLanguage.PYTHON].get_nowait() | ||||
| while not _CONTAINER_QUEUES[SupportLanguage.NODEJS].empty(): | while not _CONTAINER_QUEUES[SupportLanguage.NODEJS].empty(): | ||||
| _CONTAINER_QUEUES[SupportLanguage.NODEJS].get_nowait() | _CONTAINER_QUEUES[SupportLanguage.NODEJS].get_nowait() | ||||
| for language in SupportLanguage: | for language in SupportLanguage: | ||||
| _CONTAINER_EXECUTION_SEMAPHORES[language] = asyncio.Semaphore(size) | _CONTAINER_EXECUTION_SEMAPHORES[language] = asyncio.Semaphore(size) | ||||
| const path = require('path'); | const path = require('path'); | ||||
| const args = JSON.parse(process.argv[2]); | const args = JSON.parse(process.argv[2]); | ||||
| const mainPath = path.join(__dirname, 'main.js'); | const mainPath = path.join(__dirname, 'main.js'); | ||||
| function isPromise(value) { | |||||
| return Boolean(value && typeof value.then === 'function'); | |||||
| } | |||||
| if (fs.existsSync(mainPath)) { | if (fs.existsSync(mainPath)) { | ||||
| const { main } = require(mainPath); | |||||
| const mod = require(mainPath); | |||||
| const main = typeof mod === 'function' ? mod : mod.main; | |||||
| if (typeof main !== 'function') { | |||||
| console.error('Error: main is not a function'); | |||||
| process.exit(1); | |||||
| } | |||||
| if (typeof args === 'object' && args !== null) { | if (typeof args === 'object' && args !== null) { | ||||
| main(args).then(result => { | |||||
| if (result !== null) { | |||||
| console.log(result); | |||||
| try { | |||||
| const result = main(args); | |||||
| if (isPromise(result)) { | |||||
| result.then(output => { | |||||
| if (output !== null) { | |||||
| console.log(output); | |||||
| } | |||||
| }).catch(err => { | |||||
| console.error('Error in async main function:', err); | |||||
| }); | |||||
| } else { | |||||
| if (result !== null) { | |||||
| console.log(result); | |||||
| } | |||||
| } | } | ||||
| }).catch(err => { | |||||
| console.error('Error in main function:', err); | |||||
| }); | |||||
| } catch (err) { | |||||
| console.error('Error when executing main:', err); | |||||
| } | |||||
| } else { | } else { | ||||
| console.error('Error: args is not a valid object:', args); | console.error('Error: args is not a valid object:', args); | ||||
| } | } |