動態修改 NodeJS 程序中的變量值

若是一個 NodeJS 進程正在運行,有辦法修改程序中的變量值麼?答案是:經過 V8 的 Debugger 接口能夠!本文將詳細介紹實現步驟。html

啓動一個 HTTP Server

用簡單的 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

使 Server 進程進入 Debug 模式

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

Debugger 協議

使用node debug hostname:port 命令鏈接到進程進行 Debug 的方式比較簡單,可是要完成一些高級的功能就會到處受限,由於它只封裝了 Debugger 協議中 command 的一部分。 下面介紹一下這個簡單的協議。npm

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官方技術博客

相關文章
相關標籤/搜索