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 thatit has a different global object than the rest of the code.
One can provide the context by "contextifying" a sandboxobject. The sandboxed code treats any property on the sandbox like aglobal variable. Any changes on global variables caused by the sandboxedcode 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); // 42console.log(sandbox.y); // 17console.log(x); // 1; y is not defined.
注意: vm模块并不是实现代码安全性的一套机制。绝不要试图用其运行未经信任的代码.
Class: vm.Script#
vm.Script
类型的实例包含若干预编译的脚本,这些脚本能够在特定的沙箱(或者上下文)中被运行。
new vm.Script(code, options)#
code
需要被解析的JavaScript代码 options
filename
定义供脚本生成的堆栈跟踪信息所使用的文件名 lineOffset
定义脚本生成的堆栈跟踪信息所显示的行号偏移 columnOffset
定义脚本生成的堆栈跟踪信息所显示的列号偏移 displayErrors
当值为真的时候,假如在解析代码的时候发生错误 Error
,引起错误的行将会被加入堆栈跟踪信息timeout
定义在被终止执行之前此code被允许执行的最大毫秒数。假如执行被终止,将会抛出一个错误[Error][]。 cachedData
为源码提供一个可选的存有v8代码缓存数据的Buffer。一旦提供了此Buffer,取决于v8引擎对Buffer中数据的接受状况,cachedDataRejected值将会被设为要么真要么为假。 produceCachedData
当值为真且cachedData不存在的时候,v8将会试图为code生成代码缓存数据。一旦成功,一个有V8代码缓存数据的Buffer将会被生成和储存在vm.Script返回的实例的cachedData属性里。取决于代码缓存数据是否被成功生成,cachedDataProduced的值会被设置为true或者false。
创建一个新的vm.Script对象只编译代码但不会执行它。编译过的vm.Script此后可以被多次执行。code是不绑定于任何全局对象的,相反,它仅仅绑定于每次执行它的对象。
script.runInContext(contextifiedSandbox[, options])#
contextifiedSandbox
由vm.createContext()
返回的[contextified
][]对象options
filename
定义供脚本生成的堆栈跟踪信息所使用的文件名 lineOffset
定义脚本生成的堆栈跟踪信息所显示的行号偏移 columnOffset
定义脚本生成的堆栈跟踪信息所显示的列号偏移 displayErrors
当值为真的时候,假如在解析代码的时候发生错误 Error
,引起错误的行将会被加入堆栈跟踪信息timeout
定义在被终止执行之前此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
An object that will be contextified. Ifundefined
, anew object will be created. 一个将被[contextified
][]的对象。如果是undefined
, 会生成一个新的对象options
首先给指定的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])#
在指定的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
对象, 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
来代表一个窗口的全局对象,然后所有的标签都可以在这个
sandbox
的上下文中运行。
vm.isContext(sandbox)#
当给定的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)#
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
将被编译和运行的JavaScript代码 sandbox
一个将被上下文隔离化的对象。如果是undefined, 会生成一个新的对象options
filename
定义供脚本生成的堆栈跟踪信息所使用的文件名 lineOffset
定义脚本生成的堆栈跟踪信息所显示的行号偏移 columnOffset
定义脚本生成的堆栈跟踪信息所显示的列号偏移 displayErrors
当值为真的时候,假如在解析代码的时候发生错误Error,引起错误的行将会被加入堆栈跟踪信息 timeout
定义在被终止执行之前此code被允许执行的最大毫秒数。假如执行被终止,将会抛出一个错误 Error
首先给指定的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
将被编译和运行的JavaScript代码 options
filename
定义供脚本生成的堆栈跟踪信息所使用的文件名 lineOffset
定义脚本生成的堆栈跟踪信息所显示的行号偏移 columnOffset
定义脚本生成的堆栈跟踪信息所显示的列号偏移 displayErrors
当值为真的时候,假如在解析代码的时候发生错误Error,引起错误的行将会被加入堆栈跟踪信息 timeout
定义在被终止执行之前此code被允许执行的最大毫秒数。假如执行被终止,将会抛出一个错误 Error
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
。