Node Inspector 代理實現

本文首發於 https://github.com/whxaxes/blog/issues/9html

背景

平時作 node 開發的時候,經過 node inspector 來進行斷點調試是一個很經常使用的 debug 方式。可是有幾個問題會致使咱們的調試效率下降。node

問題一:當使用 vscode 進行斷點調試時,若是應用是經過 cluster 啓動的 inspector,那麼每次當 worker 掛了重啓後,inspector 的端口都會自增。雖然在 node8.x 版本中能夠指定 inspectPort 固定調試端口,可是在 node6.x 中是不支持的。這樣會致使每次 worker 重啓了就得在 vscode 中從新指定調試端口。git

問題二:當使用 devtools 調試的時候,每次調試都須要拷貝 devtools 連接到 chrome 上調試,而上面說的端口變動問題則會致使 devtools 的連接變動,除此以外,每次從新啓動 inspector 也會致使 devtools 的連接變動,由於 websocket id 變了。github

而把上面的兩個問題簡化一下就是:web

  • 在 vscode 中調試,在 inspector 端口變動或者 websocket id 變動後可以重連。
  • 在 devtools 中調試,在 inspector 端口變動或者 websocket id 變動後可以重連。

解決方案

目前業界已經有解決方案就是 chrome 插件 Node Inspector Manager(Nim) ,不過這個只能解決在同個 inspector 端口下的應用重啓後連接更改的問題,卻沒法解決 cluster 啓動致使的端口自增問題,除非在 Nim 中提早指定好多個端口,再者 Nim 是 chrome 上的插件,對於在 vscode 中的調試卻無能爲力了。chrome

因此最佳的解決方案天然是使用 node 來作 inspector 代理,解決方案以下:json

對於第一個問題,在 vscode 上,它是會本身去調用 /json 接口獲取最新的 websocket id,而後使用新的 websocket id 鏈接到 node inspector 服務上。所以解決方法就是實現一個 tcp 代理功能作數據轉發便可。bash

對於第二個問題,因爲 devtools 是不會自動去獲取新的 websocket id 的,因此咱們須要作動態替換,因此解決方案就是代理服務去 /json 獲取 websocket id,而後在 websocket 握手的時候將 websocket id 進行動態替換到請求頭上。websocket

畫了一張流程圖:socket

image

實現步驟

1、Tcp 代理

首先,先實現一個 tcp 代理的功能,其實很簡單,就是經過 node 的 net 模塊建立一個代理端口的 Tcp Server,而後當有鏈接過來的時候,再建立一個鏈接到目標端口便可,而後就能夠進行數據的轉發了。

簡易的實現以下:

const net = require('net');
const proxyPort = 9229;
const forwardPort = 5858;

net.createServer(client => {
  const server = net.connect({
    host: '127.0.0.1',
    port: forwardPort,
  }, () => {
    client.pipe(server).pipe(client);
  });
  // 若是真要應用到業務中,還得監聽一下錯誤/關閉事件,在鏈接關閉時即時銷燬建立的 socket。
}).listen(proxyPort);

上面實現了比較簡單的一個代理服務,經過 pipe 方法將兩個服務的數據連通起來。client 有數據的時候會被轉發到 server 中,server 有數據的時候也會轉發到 client 中。

當完成這個 Tcp 代理功能以後,就已經能夠實現 vscode 的調試需求了,在 vscode 中項目下 launch.json 中指定端口爲代理端口,在 configurations 中添加配置

{
  "type": "node",
  "request": "attach",
  "name": "Attach",
  "protocol": "inspector",
  "restart": true,
  "port": 9229
}

那麼當應用重啓,或者更換 inspect 的端口,vscode 都能自動從新經過代理端口 attach 到你的應用。

2、獲取 websocketId

這一步開始,就是爲了解決 devtools 連接不變的狀況下可以從新 attach 的問題了,在啓動 node inspector server 的時候,inspector 服務還提供了一個 /json 的 http 接口用來獲取 websocket id。

這個就至關簡單了,直接發個 http 請求到目標端口的 /json,就能夠獲取到數據了:

[ { description: 'node.js instance',
    devtoolsFrontendUrl: '...',
    faviconUrl: 'https://nodejs.org/static/favicon.ico',
    id: 'e7ef6313-1ce0-4b07-b690-d3cf5274d8b0',
    title: '/Users/wanghx/Workspace/larva-team/vscode-log/index.js',
    type: 'node',
    url: 'file:///Users/wanghx/Workspace/larva-team/vscode-log/index.js',
    webSocketDebuggerUrl: 'ws://127.0.0.1:5858/e7ef6313-1ce0-4b07-b690-d3cf5274d8b0' } ]

上面數據中的 id 字段,就是咱們須要的 websocket id 了。

3、Inspector 代理

拿到了 websocket id 後,就能夠在 tcp 代理中作 websocket id 的動態替換了,首先咱們須要固定連接,所以先定一個代理連接,好比個人代理服務端口是 9229,那麼 chrome devtools 的代理連接就是:

chrome-devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=127.0.0.1:9229/__ws_proxy__

上面除了最後面的 ws=127.0.0.1:9229/__ws_proxy__ 其餘都是固定的,而最後這個也一眼就能夠看出來是 websocket 的連接。其中 __ws_proxy__則是用來佔位的,用於在 chrome devtools 向這個代理連接發起 websocket 握手請求的時候,將 __ws_proxy__ 替換成 websocket id 而後轉發到 node 的 inspector 服務上。

對上面的 tcp 代理中的 pipe 邏輯的代碼作一些小修改便可。

const through = require('through2');
...

client
      .pipe(through.obj((chunk, enc, done) => {
        if (chunk[0] === 0x47 && chunk[1] === 0x45 && chunk[2] === 0x54) {
          const content = chunk.toString();
          if (content.includes('__ws_proxy__')) {
            return done(null, Buffer.from(content.replace('__ws_proxy__', websocketId)));
          }
        }
        done(null, chunk);
      }))
      .pipe(server)
      .pipe(client);
...

經過 through2 建立一個 transform 流來對傳輸的數據進行一下更改。

簡單判斷一下 chunk 的頭三個字節是否爲GET,若是是 GET 說明這多是個 http 請求,也就多是 websocket 的協議升級請求。把請求頭打印出來就是這個樣子的:

GET /__ws_proxy__ HTTP/1.1
Host: 127.0.0.1:9229
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
Origin: chrome-devtools://devtools
Sec-WebSocket-Version: 13
...

而後將其中的路徑/__ws_proxy替換成對應的 websocketId,而後轉發到 node 的 inspector server 上,便可完成 websocket 的握手,接下來的 websocket 通訊就不須要對數據作處理,直接轉發便可。

接下來就算各類重啓應用,或者更換 inspector 的端口,都不須要更換 debug 連接,只須要再 inspector server 重啓的時候,在下圖的彈窗中

image

點擊一下 Reconnect DevTools 便可恢復 debug。

最後

上面的詳細代碼能夠在下面的 git 中找到:

相關文章
相關標籤/搜索