CDP 远程调试方案

5,457 阅读7分钟

螺旋.png

这是第 140 篇不掺水的原创,想获取更多原创好文,请搜索公众号关注我们吧~ 本文首发于政采云前端博客:CDP 远程调试方案

什么是 cdp 协议

cdp 协议简称 chrome 调试协议,是基于 scoket(websocket、usb、adb )消息的 json rpc 协议。用来调用 chrome 内部的方法实现 js,css ,dom 的开发调试。 可以将 实现了 cdp 协议的应用 看做 rpc 调用的服务端( chrome ,puppeteer), 将调试面板看做 rpc 调用的客户端(devtools)。

devtools 调试系统

完整的调试系统分别由前端,后端,协议,通道四部分组成

  • Frontend:调试器前端,如 chrome-devtools-frontend 一个 web 应用,接收 cdp 事件&发送 cdp 命令,实现 dom,network,debugger 的执行调试。
  • Backend: 实现了 cdp 协议的应用,会接收 cdp 命令&发送 cdp 事件。Chromium、V8 或 Node.js
  • Protocol:cdp 调试协议,调试器前端和后端使用此协议通信。 它分为代表被检查实体的语义方面的域。 每个域定义类型、命令(从前端发送到后端的消息)和事件(从后端发送到前端的消息)。该协议基于 json rpc 2.0 运行;
  • Channels:消息通道,后端和前端之间发送协议消息的一种方式。包括:Embedder Channel、WebSocket Channel、Chrome Extensions Channel、USB/ADB Channel。

chrome 远程调试

如上,所有能够消费发送 cdp 消息的应用都可以被 chrome-devtools-frontend 调试。chrome 集成了cdp协议,所以只需要通过消息通道与 chrome-devtools-frontend 应用 建立 socket 链接就可以被调试端调试。调试启动分为三步:

  • 启动 cdp 服务端
  • 启动 cdp 客户端
  • 创建链接

启动 cdp 服务端

以 Chrome,puppetter 为例启动 backend 应用,设置调试端口 9222。

  • chrome
 /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222 --user-data-dir=./test
  • puppetter

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({
    args: ['--remote-debugging-port=9222', '--remote-debugging-address=0.0.0.0'],
  });
  const page = await browser.newPage();
 // let res= await page.goto('https://www.baidu.com');

  console.log(browser.wsEndpoint());
  // output -> ws://127.0.0.1:57724/devtools/browser/705082a5-19e6-4e9a-b8a6-477b7b6e1bd6
})();

此时访问 http://0.0.0.0:9222/json 此时可以获取调试页面 id 和 页面的 webSocketDebuggerUrl ,代表 backend 端启动成功。

[ {
   "description": "",
   "devtoolsFrontendUrl": "/devtools/inspector.html?ws=0.0.0.0:9222/devtools/page/659E9C174913FC5670B5F6E78B6B91FD",
   "id": "659E9C174913FC5670B5F6E78B6B91FD",
   "title": "欢迎使用 Chrome",
   "type": "page",
   "url": "chrome://welcome/",
   "webSocketDebuggerUrl": "ws://0.0.0.0:9222/devtools/page/659E9C174913FC5670B5F6E78B6B91FD"
}, {
   "description": "",
   "devtoolsFrontendUrl": "/devtools/inspector.html?ws=0.0.0.0:9222/devtools/page/9902C5D5F55923DFC457B7AA423F957E",
   "faviconUrl": "https://cn.bing.com/sa/simg/favicon-2x.ico",
   "id": "9902C5D5F55923DFC457B7AA423F957E",
   "title": "必应",
   "type": "page",
   "url": "https://cn.bing.com/",
   "webSocketDebuggerUrl": "ws://0.0.0.0:9222/devtools/page/9902C5D5F55923DFC457B7AA423F957E"
}, {
   "description": "",
   "devtoolsFrontendUrl": "/devtools/inspector.html?ws=0.0.0.0:9222/devtools/page/428C9AC3F9B6AF7235CC41D849DD7589",
   "faviconUrl": "https://www.baidu.com/favicon.ico",
   "id": "428C9AC3F9B6AF7235CC41D849DD7589",
   "title": "百度一下,你就知道",
   "type": "page",
   "url": "https://www.baidu.com/",
   "webSocketDebuggerUrl": "ws://0.0.0.0:9222/devtools/page/428C9AC3F9B6AF7235CC41D849DD7589"
} ]

启动 cdp 客户端

chrome-devtools-frontend 作为一个纯 web 应用,可以通过 koa 部署访问。同时 chrome 默认也集成了 chrome-devtools-frontend 服务。在 cdp 服务端启动后可以直接访问 http://0.0.0.0:9222/devtools/inspector.html

创建链接

传入 socket_backend_url ,http://0.0.0.0:9222/devtools/inspector.html?ws=${webSocketDebuggerUrl}

或者直接访问 http://0.0.0.0:9222/ 可以看到浏览器中的的tab线程页,点击需要调试的页面,http://0.0.0.0:9222/devtools/inspector.html?ws=0.0.0.0:9222/devtools/page/9902C5D5F55923DFC457B7AA423F957E , 就可以连接 chrome-devtools-frontend 调试了。

此时打开 network,查看 ws ,可以看到 backend 端在接收 chrome-devtools-frontend 发送的 cdp 命令,并将 dom,network,资源信息返回给远程调试端

cdp协议结构

cdp 协议按域「Domain」划分能力,每个域下有 MethodEventTypes。([https://chromedevtools.github.io/devtools-protocol/tot/Debugger/](https://chromedevtools.github.io/devtools-protocol/tot/Debugger/) )

Method 对应 socket 通信的请求/响应模式,Events 对应 socket 通信的发布/订阅模式,Types 为交互中使用到的实体。

用远程执行一段 js 为例,在 chrome-devtools-frontend 控制台输入下面代码,backend 端分别收到执行三个 cdp 命令。 Runtime.compileScript->Runtime.evaluate->Runtime.compileScript

pp=function(){
  alert(244)
}
pp()
{
    "id": 47,
    "method": "Runtime.evaluate",
    "params": {
        "expression": "pp=function(){alert(244)}",
        "includeCommandLineAPI": true,
        "generatePreview": true,
        "userGesture": false,
        "awaitPromise": false,
        "throwOnSideEffect": true,
        "timeout": 500,
        "disableBreaks": true,
        "replMode": true,
        "uniqueContextId": "-3083772769491583108.6833303176535544523"
    }
}
{
    "id": 178,
    "method": "Runtime.compileScript",
    "params": {
        "expression": "pp=function(){alert(244)}",
        "sourceURL": "",
        "persistScript": false,
        "executionContextId": 7
    }
}
{
    "id": 100,
    "method": "Runtime.compileScript",
    "params": {
        "expression": "pp()",
        "sourceURL": "",
        "persistScript": false,
        "executionContextId": 7
    }
}

手动实现

H5 端通过 CDP 协议调用调试

var ws = new WebSocket('ws://127.0.0.1:9222/devtools/page/620F91C22D41B614947001C52AC55E53');

window.ppp=function(){
// 调用 Command

  debugger
  ws.onmessage = function(event) {
    console.log(event.data);
    // 获取数据:{"method": "Page.loadEventFired", "params": {"timestamp": 1402317772.874949}}
  };
  ws.send('{"id": 1, "method": "Page.navigate", "params": {"url": "http://www.github.com"}}');
}

远程调试

chrome 社区提供了基础的远程调试方案 :devtools-remote 。 分别提供 websocket 服务做消息转发 和 chrome 插件在 backend 端来监听执行发送 cdp 消息。

devtools-remote 调试插件在 background 层实现 cdp 消息监听, 响应和执行。主要依赖下面几个 api 。 Chrome Extensions Api(chrome.debugger

  • chrome.debugger.getTargets: 获取可调试目标的列表( tab_id )

  • chrome.debugger.attach: 将调试器附加到指定目标。

  • chrome.debugger.sendCommand: 将 cdp 命令发送至调试目标。

  • chrome.debugger.onEvent.addListene: 监听指定选项卡页中的所有事件,在回调函数 response cdp 消息到 scocket 服务端。

插件内部逻辑实现

  1. 点击插件(chrome.browserAction.onClicked.addListener)获取当前页 tabID ,即目标 id.
  2. 调用 chrome.debugger.attach,将调试器附加到指定目标。
  3. socket connect 连接代理 websocket 服务器。(代理服务器连接调试应用 chrome-devtools-frontend
  4. 插件连接成功: 监听从代理服务器推送的 data.request 类型消息。 调用 chrome.debugger.sendCommand 执行 cdp 协议的命令, 将执行结果返回给socket服务端消息事件名为 data.response 消息 id 保持一致)。
  5. 调用 chrome.debugger.onEvent.addListener,监听调试页的所有事件,转成 cdp 的消息结构 {method: method, params: params} ,发送消息事件名为 data.event 到代理服务器,最终转发到调试应用

socket_proxy 逻辑 代理服务器的逻辑很简单,将来自插件端 'data.response' , 'data.event' 的事件消息转发到调试应用上。 将来自调试应用 'data.request' 事件消息转发到 backend 插件端。

此外社区还有 chobitsu 这样的 cdp 解析库,提供了 Storage、Runtime、Network、DOM、DOMDebugger、DOMStorage 的能力。与需要依赖在插件 background 层执行 chrome.debugger API 的方案不同。chobitsu 在浏览器运行时环境中手动实现了cdp 协议。 基于chobitsu 的能力可以实现待调试页面通过加载 backend.js 监听浏览器事件 执行 cdp 命令,实现 dom css network 的远程调试。 具体用例可以参考 devtools-pro 。由此方案进一步联想到xss安全的重要性。

同理 react-devtools 的实现方案,也与cdp方案 类似, 在调试页面中引入 或者 通过插件插入 backend.js,监听变化发送到调试应用。

PS:之前我们有输出过在线调试的方案,链接是前端工程师生产环境 debugger 技巧,感兴趣的同学可以点开看一下。

参考文档

chrome DevTools Protocol

chrome-remote-interface

深入理解 Chrome DevTools

推荐阅读

如何落地一个智能机器人

一名练习时长 2 年零 8 个月的前端练习生自述

浅析 Snabbdom 中 vnode 和 diff 算法

如何利用 SCSS 实现一键换肤

开源作品

  • 政采云前端小报

开源地址 www.zoo.team/openweekly/ (小报官网首页有微信交流群)

  • 商品选择 sku 插件

开源地址 github.com/zcy-inc/sku…

招贤纳士

政采云前端团队(ZooTeam),一个年轻富有激情和创造力的前端团队,隶属于政采云产品研发部,Base 在风景如画的杭州。团队现有 60 余个前端小伙伴,平均年龄 27 岁,近 4 成是全栈工程师,妥妥的青年风暴团。成员构成既有来自于阿里、网易的“老”兵,也有浙大、中科大、杭电等校的应届新人。团队在日常的业务对接之外,还在物料体系、工程平台、搭建平台、性能体验、云端应用、数据分析及可视化等方向进行技术探索和实战,推动并落地了一系列的内部技术产品,持续探索前端技术体系的新边界。

如果你想改变一直被事折腾,希望开始能折腾事;如果你想改变一直被告诫需要多些想法,却无从破局;如果你想改变你有能力去做成那个结果,却不需要你;如果你想改变你想做成的事需要一个团队去支撑,但没你带人的位置;如果你想改变既定的节奏,将会是“5 年工作时间 3 年工作经验”;如果你想改变本来悟性不错,但总是有那一层窗户纸的模糊… 如果你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的自己。如果你希望参与到随着业务腾飞的过程,亲手推动一个有着深入的业务理解、完善的技术体系、技术创造价值、影响力外溢的前端团队的成长历程,我觉得我们该聊聊。任何时间,等着你写点什么,发给 ZooTeam@cai-inc.com