我想使用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秒)?
提前致谢!
答案 0 :(得分:22)
您需要在单独的流程中运行它,例如:
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"
。
答案 1 :(得分:6)
您可以将“脚本断开器”嵌入到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...
}
击> <击> 撞击>
<强>更新强>
经过多次测试后,我得出的结论是,可靠的方法是使用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);
});
这种方法可以实现以下目标:
答案 2 :(得分:4)
是的,现在可以实现这一点,因为我向节点timeout
模块添加了vm
参数支持。您可以简单地将毫秒超时值传递给runInNewContext()
,如果代码未在指定的时间内完成执行,则会抛出异常。
请注意,这并不意味着运行不受信任的代码的任何类型的安全模型。这简单地允许您超时您信任或以其他方式保护的代码。
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
答案 3 :(得分:3)
您可能需要查看Threads a 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);
答案 4 :(得分:0)
在较新版本的Node.js(v0.12
及更高版本)中,您可以将超时选项传递给vm.runInNewContext
。
这还没有在任何稳定的节点版本中,但是如果你想使用最新的不稳定版本(v0.11.13
),你可以像这样传递一个超时参数:
vm.runInNewContext('while(1) {}', {}, {timeout: '1000'});
现在,1000毫秒后脚本将抛出超时错误。你可以这样抓住它:
try {
vm.runInNewContext('while(1) {}', {}, {timeout: '1000'});
}
catch(e) {
console.log(e); // Script execution timed out.
}