动态修改 NodeJS 程序中的变量值 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
OneAPM

动态修改 NodeJS 程序中的变量值

  •  8
     
  •   OneAPM 2015 年 7 月 1 日 4422 次点击
    这是一个创建于 3950 天前的主题,其中的信息可能已经有所发展或是发生改变。

    如果一个 NodeJS 进程正在运行,有办法修改程序中的变量值么?答案是:通过 V8 的 Debugger 接口可以!本文将详细介绍实现步骤。

    启动一个 HTTP Server

    用简单的 Hello World 做例子吧,不过略作修改。在 global 下放一个变量 message, 然后打印出来:
    ```
    // 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!"

    使 Server 进程进入 Debug 模式

    V8 引擎在实现的时候留了 Debugger 接口。 通过命令 node --debug-brk=5858 [filename] 可以启动一个脚本,并且立即进入 Debug 模式。

    那么如果是已经运行着的 NodeJS 程序,可以进入 Debug 模式吗?也是可以的,在操作系统下给 NodeJS 的进程发一个 SIGUSR1 信号,可以让进程进入 Debug 模式。 进入 Debug 模式的进程会在本地启动一个 TCP Server 并且默认监听5858 端口。

    此时在另一个命令行窗口执行命令 node debug localhost:5858 就可以连接到 Debugger 调试端口, 并且可以使用很多常用的 Debug 命令,比如 c继续执行,s 步入, o步出等。

    Debugger 协议

    使用node debug hostname:port 命令连接到进程进行 Debug 的方式比较简单,但是要完成一些高级的功能就会处处受限,因为它只封装了 Debugger 协议中 command 的一部分。 下面介绍一下这个简单的协议。

    Client 和 Server 的通讯是通过 TCP 进行的。 DebugClient 第一次连接到 DebugServer 的时候会拿到一些 Header,比如 Node 版本, V8 版本等。后面紧跟着一个空的消息1

    消息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 字符串,这是由协议所规定的。

    消息2

    Content-Length: 46\r\n \r\n {"command":"version","type":"request","seq":1} 

    消息2的类型( type )是request,代表这是 Client 发给 Server 的命令,其他的可能值是 responseevent分别代表 Server 对 Client 的相应,和 Server 端发生的事件。

    消息3

    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 Client

    了解了 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 () {
    // 连接成功
    });
    ```

    通过 Debugger 接口执行命令

    接下来我们来看看如何修改这个 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 相关的功能集成

    关于

    1. 本文相关的源码在: https://github.com/wyvernnot/interference_demo;

    2. 如果你也对 Debugger 协议感兴趣,可以安装 oneapm-debugger 这个工具,它可以帮助你查看 Debug 过程中所有实际发送的数据。 oneapm-debugger


    本文系OneAPM工程师编译整理。OneAPM是中国基础软件领域的新兴领军企业,能帮助企业用户和开发者轻松实现:缓慢的程序代码和SQL语句的实时抓取。想阅读更多技术文章,请访问OneAPM官方技术博客

    9 条回复    2015-07-01 18:22:44 +08:00
    angelsoul
        1
    angelsoul  
       2015 年 7 月 1 日
    真是拼
    OneAPM
        2
    OneAPM  
    OP
       2015 年 7 月 1 日
    @angelsoul n(**)n
    realpg
        3
    realpg  
    PRO
       2015 年 7 月 1 日
    这个有啥用?投票数据在不改变公证监控下的程序上的情况下作弊?
    TakanashiAzusa
        4
    TakanashiAzusa  
       2015 年 7 月 1 日
    建议贵司设计给换个头像。。彩虹色背景+字母,这是几年前的流行趋势哦。。
    loveyu
        5
    loveyu  
       2015 年 7 月 1 日
    感觉这个公司太拼了
    VirgilMing
        6
    VirgilMing  
       2015 年 7 月 1 日
    @TakanashiAzusa 不不不,这明显是跟 #LoveWins 的风。最多两天前他们 Logo 还是蓝底的。
    OneAPM
        7
    OneAPM  
    OP
       2015 年 7 月 1 日
    @VirgilMing Bingo!
    TakanashiAzusa
        8
    TakanashiAzusa  
       2015 年 7 月 1 日
    @VirgilMing 这我知道,但是彩虹底配这个字。看着太太太太有历史感了。
    iyangyuan
        9
    iyangyuan  
       2015 年 7 月 1 日 via iPhone
    西小口的公司,老板人很好
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2852 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 78ms UTC 01:14 PVG 09:14 LAX 18:14 JFK 21:14
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86