Node.js vm:如何取消Script.runInNewContext()?

我想使用vm模块作为运行外部代码的安全方式。 它工作得很好,但还有一个问题:

 var UNKNOWN_CODE = "while(true){}"; var vm = require("vm"); var obj = {}; var ctx = vm.createContext(obj); var script = vm.createScript(UNKNOWN_CODE); script.runInNewContext(ctx); console.log("finished"); //never executed 

有没有办法取消执行(例如,如果持续5秒以上)?

提前致谢!

是的,这是现在可能的,因为我添加了对Node vm模块的timeout参数支持。 您可以简单地将毫秒超时值传递给runInNewContext() ,如果代码没有在指定的时间内完成执行,则会引发exception。

请注意,这并不意味着运行不受信任的代码的任何种类的安全模型。 这只允许您超时您信任或以其他方式保护的代码。

 var vm = require("vm"); try { vm.runInNewContext("while(true) {}", {}, "loop", 1000); } catch (e) { // Exception thrown after 1000ms } console.log("finished"); // Will now be executed 

正是你期望的:

 $ time ./node test.js finished real 0m1.069s user 0m1.047s sys 0m0.017s 

您需要在一个单独的过程中运行它,例如:

master.js:

 var cluster = require('cluster'); cluster.setupMaster({ exec : "runner.js", args : process.argv.slice(2), silent : false }); //This will be fired when the forked process becomes online cluster.on( "online", function(worker) { var timer = 0; worker.on( "message", function(msg) { clearTimeout(timer); //The worker responded in under 5 seconds, clear the timeout console.log(msg); worker.destroy(); //Don't leave him hanging }); timer = setTimeout( function() { worker.destroy(); //Give it 5 seconds to run, then abort it console.log("worker timed out"); }, 5000); worker.send( 'while(true){}' ); //Send the code to run for the worker }); cluster.fork(); 

runner.js:

 //The runner.js is ran in a separate process and just listens for the message which contains code to be executed process.on('message', function( UNKNOWN_CODE ) { var vm = require("vm"); var obj = {}; var ctx = vm.createContext(obj); var script = vm.createScript(UNKNOWN_CODE); script.runInNewContext(ctx); process.send( "finished" ); //Send the finished message to the parent process }); 

要运行这个例子,把这些文件放在同一个文件夹中,然后运行

 node master.js 

5秒后应该看到“工作超时”消息。 如果将其更改为'while(false){}'则工作人员将立即执行代码,而应该看到"finished"

群集文档

您可以将“脚本破坏者”embedded到UNKNOWN_CODE中。 就像是:

 ;setTimeout(function() { throw new Error("Execution time limit reached!") }, 2000); 

所以,整个事情看起来像这样:

 var UNKNOWN_CODE = "while(true){}"; var scriptBreaker = ';setTimeout(function() { throw new Error("Execution time limit reached!") }, 2000);'; var vm = require("vm"); var obj = {}; var ctx = vm.createContext(obj); var script = vm.createScript(scriptBreaker + UNKNOWN_CODE); try { script.runInNewContext(ctx); console.log("Finished"); } catch (err) { console.log("Timeout!"); // Handle Timeout Error... } 

更新:

经过更多的testing,我得出的结论是可靠的方法将是使用指针Esailija进程。 不过,我做的有点不同。

在主应用程序中,我有这样的代码:

 var cp = require('child_process'); function runUnsafeScript(script, callback) { var worker = cp.fork('./script-runner', [script]); worker.on('message', function(data) { worker.kill(); callback(false, data); }); worker.on('exit', function (code, signal) { callback(new Error(code), false); }); worker.on('error', function (err) { callback(err, false); }); setTimeout(function killOnTimeOut() { worker.kill(); callback(new Error("Timeout"), false); }, 5000); } 

在script-runner.js中看起来如下所示:

 var vm = require("vm"); var script = vm.createScript( process.argv[2] ); var obj = { sendResult:function (result) { process.send(result); process.exit(0); } }; var context = vm.createContext(obj); script.runInNewContext(context); process.on('uncaughtException', function(err) { process.exit(1); }); 

这种方法可以达到以下目标:

  • 在有限的上下文中运行脚本
  • 避免死循环和exception问题
  • 同时运行许多(受硬件限制的)不安全的脚本,以免互相干扰
  • 将脚本执行结果传递给主应用程序进行进一步处理

你可能想检查线程一个Gogo 。 不幸的是,它还没有更新到0.8.x。

但是,正如@Esailija所述, 除非在另一个进程中,否则无法安全地运行外部代码

 var Threads = require('threads_a_gogo'); var t = Threads.create(); t.eval("while(true) { console.log('.'); }"); setTimeout(function() { t.destroy(); console.log('finished'); }, 1000); 

在更新版本的Node.js( v0.12及更高版本)中,您将能够将超时选项传递给vm.runInNewContext

这还没有稳定的节点版本,但是如果你想使用最新的unstable版本( v0.11.13 ),你可以像这样传递一个timeout参数:

 vm.runInNewContext('while(1) {}', {}, {timeout: '1000'}); 

现在,在1000毫秒之后,该脚本将引发超时错误。 你可以这样捕捉它:

 try { vm.runInNewContext('while(1) {}', {}, {timeout: '1000'}); } catch(e) { console.log(e); // Script execution timed out. }