若是一个 NodeJS 进程正在运行,有办法修改程序中的变量值么?答案是:经过 V8 的 Debugger 接口能够!本文将详细介绍实现步骤。html
用简单的 Hello World 作例子吧,不过略做修改。在 global
下放一个变量 message, 而后打印出来:node
// message content will be modified ! global.message = "hello world!"; var server = require('http').createServer(function (req, res) { res.end(global.message); }).listen(8001); console.log('pid = %d', process.pid);
用命令启动 Server
,此时,经过用浏览器访问 http://localhost:8001 能够看到网页内容是 hello world!
。 接下来咱们将尝试在不改变代码,不重启进程的状况下把 message
换成 "hello bugs!"
。git
V8 引擎在实现的时候留了 Debugger 接口。 经过命令 node --debug-brk=5858 [filename]
能够启动一个脚本,而且当即进入 Debug 模式。程序员
那么若是是已经运行着的 NodeJS 程序,能够进入 Debug 模式吗?也是能够的,在操做系统下给 NodeJS 的进程发一个 SIGUSR1
信号,可让进程进入 Debug 模式。 进入 Debug 模式的进程会在本地启动一个 TCP Server
而且默认监听5858
端口。github
此时在另外一个命令行窗口执行命令 node debug localhost:5858
就能够链接到 Debugger 调试端口, 而且可使用不少经常使用的 Debug 命令,好比 c
继续执行,s
步入, o
步出等。express
使用node debug hostname:port
命令链接到进程进行 Debug 的方式比较简单,可是要完成一些高级的功能就会到处受限,由于它只封装了 Debugger 协议中 command 的一部分。 下面介绍一下这个简单的协议。npm
Client 和 Server 的通信是经过 TCP 进行的。 DebugClient 第一次链接到 DebugServer 的时候会拿到一些 Header,好比 Node 版本, V8 版本等。后面紧跟着一个空的消息1浏览器
Type: connect\r\n V8-Version: 3.28.71.19\r\n Protocol-Version: 1\r\n Embedding-Host: node v0.12.4\r\n Content-Length: 0\r\n \r\n
消息实体由 Header 和 Body 组成,消息1的 Body 为空,因此 Header 中对应的 Content-Length 为 0。而在下面这个例子里,Body 为一个单行的 JSON 字符串,这是由协议所规定的。服务器
Content-Length: 46\r\n \r\n {"command":"version","type":"request","seq":1}
消息2的类型( type )是request
,表明这是 Client 发给 Server 的命令,其余的可能值是 response
和 event
分别表明 Server 对 Client 的相应,和 Server 端发生的事件。闭包
Content-Length: 137\r\n \r\n {"seq":1,"request_seq":1,"type":"response","command":"version","success":true,"body":{"V8Version":"3.28.71.19"},"refs":[],"running":true}
消息2是 Client 发送给 Server的,消息3是 Server 对 Client 的相应,那么如何判断消息3是否是消息2的结果呢?能够看到消息2中的 seq
值是1,而 消息3中的request_seq
值是1。 Debugger 协议正是经过这两个值把异步返回的结果和请求一一对应起来的。
Debugger 协议就是这么的简单。
了解了 Debugger 协议后,相信好奇心强的程序员已经跃跃欲试本身实现一个了。本着不重复发明轮子的原则开始在网上找实现,找了很久找到这个库 pDebug, 惋惜这个库已经很久不更新了。后来经过阅读 node-inspector
的源码才发现,其实 NodeJS 自带了一个 Debugger 模块, 相关代码在 _debugger 模块里(源码),因为模块名是以 _ 开头的,因此网上找不到它的 API,好在代码注释写的很是详细,很快就能上手。
咱们须要的正是这个模块下的 Client, 而 Client 实际上是继承于 Socket 的.
var Client = require('_debugger').Client; var client = new Client(); client.connect(5858); client.on('ready', function () { // 链接成功 });
接下来咱们来看看如何修改这个 global 的变量,代码以下
function modifyTheMessage(newMessage) { var msg = { 'command': 'evaluate', 'arguments': { 'expression': 'global.message="' + newMessage + '"', 'global': true } }; client.req(msg, function (err, body, res) { console.log('modified to %s', newMessage); }); }
client.req
方法封装了 type=request
消息类型 和seq
自增的逻辑,所以在构造 msg
JSON对象的时候不须要指明这两个属性。 咱们要修改 message 其实就是在 JavaScript 调用的顶层执行 global.message=newMessage
此时,再访问http://localhost:8001
能够看到网页上显示的内容已经由 'hello world!'
变成了 'hello bugs!'
,是否是很神奇。
这种方式也带来了不少可能性:
动态修改配置
线上的服务器不用重启就能够应用新的配置
模块注入
经过其余任意语言编写的应用程序为已经运行的 NodeJS 进程注入新的模块
性能监控
能够剥离用户线上代码对第三方性能监控模块的直接依赖
错误监控
发生异常时,经过 Debugger 能够抓到发生错误的函数和行号,而且抓取各个调用栈中的每个变量,即便是在闭包里
Chrome 调试
因为 Chrome 也是基于 V8 的,上述方法也能够用于 Chrome 相关的功能集成
若是你也对 Debugger 协议感兴趣,能够安装 oneapm-debugger 这个工具,它能够帮助你查看 Debug 过程当中全部实际发送的数据。 oneapm-debugger