若是一個 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