### 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
| @@ -213,6 +213,42 @@ To add Node.js dependencies: | |||
| --- | |||
| ## 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 | |||
| ### ❓Sandbox Not Working? | |||
| @@ -15,24 +15,29 @@ | |||
| # | |||
| import base64 | |||
| from core.container import _CONTAINER_EXECUTION_SEMAPHORES | |||
| from core.logger import logger | |||
| from fastapi import Request | |||
| from models.enums import ResultStatus | |||
| from models.enums import ResultStatus, SupportLanguage | |||
| from models.schemas import CodeExecutionRequest, CodeExecutionResult | |||
| from services.execution import execute_code | |||
| from services.limiter import limiter | |||
| from services.security import analyze_code_security | |||
| from core.container import _CONTAINER_EXECUTION_SEMAPHORES | |||
| async def healthz_handler(): | |||
| return {"status": "ok"} | |||
| @limiter.limit("5/second") | |||
| async def run_code_handler(req: CodeExecutionRequest, request: Request): | |||
| logger.info("🟢 Received /run request") | |||
| async with _CONTAINER_EXECUTION_SEMAPHORES[req.language]: | |||
| 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) | |||
| if not is_safe: | |||
| issue_details = "\n".join([f"Line {lineno}: {issue}" for issue, lineno in issues]) | |||
| @@ -20,4 +20,5 @@ from api.handlers import healthz_handler, run_code_handler | |||
| router = APIRouter() | |||
| router.get("/healthz")(healthz_handler) | |||
| router.post("/run")(run_code_handler) | |||
| router.post("/run")(run_code_handler) | |||
| @@ -26,7 +26,7 @@ from core.logger import logger | |||
| _CONTAINER_QUEUES: dict[SupportLanguage, Queue] = {} | |||
| _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]: | |||
| @@ -38,7 +38,7 @@ async def init_containers(size: int) -> tuple[int, int]: | |||
| _CONTAINER_QUEUES[SupportLanguage.PYTHON].get_nowait() | |||
| while not _CONTAINER_QUEUES[SupportLanguage.NODEJS].empty(): | |||
| _CONTAINER_QUEUES[SupportLanguage.NODEJS].get_nowait() | |||
| for language in SupportLanguage: | |||
| _CONTAINER_EXECUTION_SEMAPHORES[language] = asyncio.Semaphore(size) | |||
| @@ -82,20 +82,40 @@ const fs = require('fs'); | |||
| const path = require('path'); | |||
| const args = JSON.parse(process.argv[2]); | |||
| const mainPath = path.join(__dirname, 'main.js'); | |||
| function isPromise(value) { | |||
| return Boolean(value && typeof value.then === 'function'); | |||
| } | |||
| 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) { | |||
| 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 { | |||
| console.error('Error: args is not a valid object:', args); | |||
| } | |||