Node.js v8.x 中文文档
目录
- vm (虚拟机)
- Class: vm.Script
- vm.createContext([sandbox])
- vm.isContext(sandbox)
- vm.runInContext(code, contextifiedSandbox[, options])
- vm.runInDebugContext(code)
- vm.runInNewContext(code[, sandbox][, options])
- vm.runInThisContext(code[, options])
- Example: Running an HTTP Server within a VM
- What does it mean to "contextify" an object?
vm (虚拟机)#
vm
模块提供了一系列 API 用于在 V8 虚拟机环境中编译和运行代码。
JavaScript 代码可以被编译并立即运行,或编译、保存然后再运行。
A common use case is to run the code in a sandboxed environment. The sandboxed code uses a different V8 Context, meaning that it has a different global object than the rest of the code.
One can provide the context by "contextifying" a sandbox object. The sandboxed code treats any property on the sandbox like a global variable. Any changes on global variables caused by the sandboxed code are reflected in the sandbox object.
const vm = require('vm');
const x = 1;
const sandbox = { x: 2 };
vm.createContext(sandbox); // Contextify the sandbox.
const code = 'x += 40; var y = 17;';
// x and y are global variables in the sandboxed environment.
// Initially, x has the value 2 because that is the value of sandbox.x.
vm.runInContext(code, sandbox);
console.log(sandbox.x); // 42
console.log(sandbox.y); // 17
console.log(x); // 1; y is not defined.
注意: vm模块并不是实现代码安全性的一套机制。 绝不要试图用其运行未经信任的代码.
Class: vm.Script#
vm.Script
类型的实例包含若干预编译的脚本,这些脚本能够在特定的沙箱(或者上下文)中被运行。
new vm.Script(code, options)#
code
<string> 需要被解析的JavaScript代码options
filename
<string> 定义供脚本生成的堆栈跟踪信息所使用的文件名lineOffset
<number> 定义脚本生成的堆栈跟踪信息所显示的行号偏移columnOffset
<number> 定义脚本生成的堆栈跟踪信息所显示的列号偏移displayErrors
<boolean> 当值为真的时候,假如在解析代码的时候发生错误Error
,引起错误的行将会被加入堆栈跟踪信息timeout
<number> 定义在被终止执行之前此code被允许执行的最大毫秒数。假如执行被终止,将会抛出一个错误[Error][]。cachedData
<Buffer> 为源码提供一个可选的存有v8代码缓存数据的Buffer。一旦提供了此Buffer,取决于v8引擎对Buffer中数据的接受状况,cachedDataRejected值将会被设为要么 真要么为假。produceCachedData
<boolean> 当值为真且cachedData不存在的时候,v8将会试图为code生成代码缓存数据。一旦成功,一个有V8代码缓存数据的Buffer将会被生成和储存在vm.Script返回的实例的cachedData属性里。 取决于代码缓存数据是否被成功生成,cachedDataProduced的值会被设置为true或者false。
创建一个新的vm.Script对象只编译代码但不会执行它。编译过的vm.Script此后可以被多次执行。code是不绑定于任何全局对象的,相反,它仅仅绑定于每次执行它的对象。
script.runInContext(contextifiedSandbox[, options])#
contextifiedSandbox
<Object> 由vm.createContext()
返回的[contextified
][]对象options
<Object>filename
<string> 定义供脚本生成的堆栈跟踪信息所使用的文件名lineOffset
<number> 定义脚本生成的堆栈跟踪信息所显示的行号偏移columnOffset
<number> 定义脚本生成的堆栈跟踪信息所显示的列号偏移displayErrors
<boolean> 当值为真的时候,假如在解析代码的时候发生错误Error
,引起错误的行将会被加入堆栈跟踪信息timeout
<number> 定义在被终止执行之前此code被允许执行的最大毫秒数。假如执行被终止,将会抛出一个错误Error
。breakOnSigint
: 若值为真,当收到SIGINT
(Ctrl+C)事件时,代码会被终止执行。此外,通过process.on("SIGINT")
方法所设置的消息响应机制在代码被执行时会被屏蔽,但代码被终止后会被恢复。如果执行被终止,一个错误Error
会被抛出。
在指定的contextifiedSandbox
中执行vm.Script
对象中被编译后的代码并返回其结果。被执行的代码无法获取本地作用域。
以下的例子会编译一段代码,该代码会递增一个全局变量,给另外一个全局变量赋值。同时该代码被编译后会被多次执行。全局变量会被置于sandbox
对象内。
const util = require('util');
const vm = require('vm');
const sandbox = {
animal: 'cat',
count: 2
};
const script = new vm.Script('count += 1; name = "kitty";');
const context = vm.createContext(sandbox);
for (let i = 0; i < 10; ++i) {
script.runInContext(context);
}
console.log(util.inspect(sandbox));
// { animal: 'cat', count: 12, name: 'kitty' }
注意: 使用timeout
或者breakOnSigint
选项会导致若干新的事件循环以及对应的线程,这有一个非零的性能消耗。
script.runInNewContext([sandbox[, options]])#
sandbox
<Object> An object that will be contextified. Ifundefined
, a new object will be created. 一个将被[contextified
][]的对象。如果是undefined
, 会生成一个新的对象options
<Object>
首先给指定的sandbox
提供一个隔离的上下文, 再在此上下文中执行vm.Script
中被编译的代码,最后返回结果。运行中的代码无法获取本地作用域。
以下的例子会编译一段代码,该代码会递增一个全局变量,给另外一个全局变量赋值。同时该代码被编译后会被多次执行。全局变量会被置于各个独立的sandbox
对象内。
const util = require('util');
const vm = require('vm');
const script = new vm.Script('globalVar = "set"');
const sandboxes = [{}, {}, {}];
sandboxes.forEach((sandbox) => {
script.runInNewContext(sandbox);
});
console.log(util.inspect(sandboxes));
// [{ globalVar: 'set' }, { globalVar: 'set' }, { globalVar: 'set' }]
script.runInThisContext([options])#
options
<Object>
在指定的global
对象的上下文中执行vm.Script
对象里被编译的代码并返回其结果。被执行的代码虽然无法获取本地作用域,但是能获取global
对象。
以下的例子会编译一段代码,该代码会递增一个global
变量。同时该代码被编译后会被多次执行。
const vm = require('vm');
global.globalVar = 0;
const script = new vm.Script('globalVar += 1', { filename: 'myfile.vm' });
for (let i = 0; i < 1000; ++i) {
script.runInThisContext();
}
console.log(globalVar);
// 1000
vm.createContext([sandbox])#
sandbox
<Object>
给定一个sandbox
对象, vm.createContext()
会设置此sandbox
,从而让它具备在vm.runInContext()
或者script.runInContext()
中被使用的能力。对于此二方法中所调用的脚本,他们的全局对象不仅拥有我们提供的sandbox
对象的所有属性,同时还有任何global object所拥有的属性。对于这些脚本之外的所有代码,他们的全局变量将保持不变。
const util = require('util');
const vm = require('vm');
global.globalVar = 3;
const sandbox = { globalVar: 1 };
vm.createContext(sandbox);
vm.runInContext('globalVar *= 2;', sandbox);
console.log(util.inspect(sandbox)); // { globalVar: 2 }
console.log(util.inspect(globalVar)); // 3
如果未提供sandbox
(或者传入undefined
),那么会返回一个全新的,空的,上下文隔离化后的sandbox
对象。
vm.createContext()
主要是用于创建一个能运行多个脚本的sandbox
。比如说,在模拟一个网页浏览器时,此方法可以被用于创建一个单独的sandbox
来代表一个窗口的全局对象,然后所有的<script>
标签都可以在这个sandbox
的上下文中运行。
vm.isContext(sandbox)#
sandbox
<Object>
当给定的sandbox
对象已经被vm.createContext()
上下文隔离化,则返回真。
vm.runInContext(code, contextifiedSandbox[, options])#
vm.runInContext()
在指定的contextifiedSandbox
的上下文里执行vm.Script对象中被编译后的代码并返回其结果。被执行的代码无法获取本地作用域。contextifiedSandbox
必须是事先被vm.createContext()
上下文隔离化过的对象。
以下例子使用一个单独的, 上下文隔离化过的对象来编译并运行几个不同的脚本:
const util = require('util');
const vm = require('vm');
const sandbox = { globalVar: 1 };
vm.createContext(sandbox);
for (let i = 0; i < 10; ++i) {
vm.runInContext('globalVar *= 2;', sandbox);
}
console.log(util.inspect(sandbox));
// { globalVar: 1024 }
vm.runInDebugContext(code)#
code
<string> 要被编译和执行的JavaScript代码
vm.runInDebugContext()
会在V8的调试上下文中编译并执行code
。此方法主要在需要获取V8Debug
对象的时候使用。
const vm = require('vm')
const Debug = vm.runInDebugContext('Debug');
console.log(Debug.findScript(process.emit).name); // 'events.js'
console.log(Debug.findScript(process.exit).name); // 'internal/process.js'
注意: 调试上下文和对象从本质而言是从属于V8调试器的,故有可能会在没有事先警告的情况下被改变(甚至被移除)
Debug
对象另外还可以通过特定于V8的--expose_debug_as
命令行选项获得。
vm.runInNewContext(code[, sandbox][, options])#
code
<string> 将被编译和运行的JavaScript代码options
首先给指定的sandbox(若为undefined
,则会新建一个sandbox
)提供一个隔离的上下文, 再在此上下文中执行vm.Script中被编译的代码,最后返回结果。运行中的代码无法获取本地作用域。
以下的例子会编译一段代码,该代码会递增一个全局变量,给另外一个全局变量赋值。同时该代码被编译后会被多次执行。全局变量会被置于sandbox
对象内。
const util = require('util');
const vm = require('vm');
const sandbox = {
animal: 'cat',
count: 2
};
vm.runInNewContext('count += 1; name = "kitty"', sandbox);
console.log(util.inspect(sandbox));
// { animal: 'cat', count: 3, name: 'kitty' }
vm.runInThisContext(code[, options])#
code
<string> 将被编译和运行的JavaScript代码options
vm.runInThisContext()
在当前的global
对象的上下文中编译并执行code
,最后返回结果。运行中的代码无法获取本地作用域,但可以获取当前的global
对象。
下面的例子演示了使用vm.runInThisContext()
和JavaScript的eval()
方法去执行相同的一段代码:
const vm = require('vm');
let localVar = 'initial value';
const vmResult = vm.runInThisContext('localVar = "vm";');
console.log('vmResult:', vmResult);
console.log('localVar:', localVar);
const evalResult = eval('localVar = "eval";');
console.log('evalResult:', evalResult);
console.log('localVar:', localVar);
// vmResult: 'vm', localVar: 'initial value'
// evalResult: 'eval', localVar: 'eval'
正因vm.runInThisContext()
无法获取本地作用域,故localVar
的值不变。相反,eval()
确实能获取本地作用域,所以localVar
的值被改变了。如此看来,vm.runInThisContext()
更像是间接的执行eval()
, 就像(0, eval)('code')
Example: Running an HTTP Server within a VM#
在使用script.runInThisContext()
或者vm.runInThisContext()
时,目标代码是在当前的V8全局对象的上下文中执行的。被传入此虚拟机上下文的目标代码会有自己独立的作用域。
要想用http
模块搭建一个简易的服务器,被传入的代码必须要么自己执行require('http')
,要么引用一个http
,比如:
'use strict';
const vm = require('vm');
const code = `
((require) => {
const http = require('http');
http.createServer((request, response) => {
response.writeHead(200, { 'Content-Type': 'text/plain' });
response.end('Hello World\\n');
}).listen(8124);
console.log('Server running at http://127.0.0.1:8124/');
})`;
vm.runInThisContext(code)(require);
注意: 上述例子中的require()
和导出它的上下文共享状态。这在运行未经认证的代码时可能会引入风险,比如在不理想的情况下修改上下文中的对象。
What does it mean to "contextify" an object?#
所有用Node.js所运行的JavaScript代码都是在一个“上下文”的作用域中被执行的。 根据V8 Embedder's Guide:
在V8中,一个上下文是一个执行环境,它允许分离的,无关的JavaScript应用在一个V8的单例中被运行。 你必须明确地指定用于运行所有JavaScript代码的上下文。
当调用vm.createContext()
时,传入的sandbox
对象(或者新建的一个sandbox
对象,若原sandbox
为undefined
)在底层会和一个新的V8上下文实例联系上。这个V8上下文在一个隔离的全局环境中,使用vm
模块的方法运行code
。创建V8上下文和使之联系上sandbox
的过程在此文档中被称作为"上下文隔离化"sandbox
。
- 断言测试
- 异步钩子(Async Hooks)
- 缓存(Buffer)
- C++ 插件
- C/C++ 插件 - N-API
- 子进程
- 集群(Cluster)
- 命令行参数
- 控制台(Console)
- 加密(Crypto)
- 调试器
- 废弃的 API
- DNS
- 域(Domain)
- ECMAScript 模块
- 错误(Errors)
- 事件(Events)
- 文件系统
- 全局对象(Globals)
- HTTP
- HTTP/2
- HTTPS
- 检查工具(Inspector)
- 国际化
- 模块(Modules)
- 网络(Net)
- 操作系统(OS)
- 路径(Path)
- 性能钩子(Performance Hooks)
- 进程
- Punycode
- 查询字符串
- 逐行读取
- 交互式解释器(REPL)
- 流(Stream)
- 字符串解码
- 定时器(Timers)
- 安全传输层(TLS/SSL)
- 事件跟踪(Tracing)
- TTY
- UDP / 数据报
- URL
- 工具集
- V8
- 虚拟机(VM)
- 压缩(ZLIB)