Open BlackHole1 opened 5 years ago
因为正在开发一个项目,而这个项目使用到了puppeteer,其中有个功能是在puppeteer打开的chrome里打开多个Tab,并进行管理。 虽然puppeteer可以打开多个网站,但是并不利于管理,所有我使用的是插件的方式,通过插件来打开多网站,并进行管理。
puppeteer
Tab
但是这里有个需求是,当网站崩溃时,我要做出一些操作。但是目前网上没有一个好的办法去监听当前网站是否崩溃。
可能有同学会说:puppeteer不是提供了一个page.on('error', fn)的方法,来进行监听么?
page.on('error', fn)
请注意上文中提到的,使用插件打开多个网站,puppeteer提供的方法只能对自己打开的网站起作用,没有使用puppeteer打开的网站,page.on('error', fn)方法无能为力。
这个方法是由我同事Haitao提出来的思路。
在当前网站上运行一个Service Workers,因为在运行的时候Service Workers会再启动一个单独的进程,当前网站和Service Workers是两个单独的进程。也就是说当网站崩溃时,并不影响Service Workers进程。所以可通过心跳检测来进行判断网站是否崩溃。
Service Workers
心跳检测
网上也有阿里的同学写的相关文章:如何监控网页崩溃?
但是我并没有使用这个方式,因为当Service Workers崩溃了,那就没有任何办法了,可能有同学会说:网站和Service Workers互相发心跳检测。这可能是一种办法,但是我不太喜欢这种方式。
在开始前,我们先去看下puppeteer的源码,为什么puppeteer可以监听到网页的崩溃。
其代码在lib/Page.js文件里。
lib/Page.js
首先可以看到Page是一个Class,其继承了EventEmitter,EventEmitter为page提供了on方法,也就是我们之前看到的:page.on('error', fn)
Class
EventEmitter
page
on
从这里就可知,在Page Class里,有地方调用了this.emit('error')来触发error event。搜了一下,发现其代码在_onTargetCrashed方法里。如:
Page Class
this.emit('error')
error event
_onTargetCrashed
触发crash的方法,我们找到了。那这个_onTargetCrashed又是在哪触发的呢?
crash
可见,是一个叫client的方法监听到了Inspector.targetCrashed事件,而这个事件触发了_onTargetCrashed函数,clinet方法就不再跟了,因为跳地方较多,只需要知道,最终client是一个websocket的产物。而websocket创建的代码在lib/Launcher.js里。代码位置
client
Inspector.targetCrashed
clinet
websocket
lib/Launcher.js
注意这两行:
const transport = new PipeTransport((chromeProcess.stdio[3]), (chromeProcess.stdio[4])); connection = new Connection('', transport, slowMo);
chromeProcess是nodejs中的spawn产物,代码为:
chromeProcess
nodejs
spawn
代码位置
const chromeProcess = childProcess.spawn( chromeExecutable, chromeArguments, { detached: process.platform !== 'win32', env, stdio } );
其中chromeArguments是chrome启动的参数列表,此列表是有一个--remote-debugging-的:
chromeArguments
chrome
--remote-debugging-
if (!chromeArguments.some(argument => argument.startsWith('--remote-debugging-'))) chromeArguments.push(pipe ? '--remote-debugging-pipe' : '--remote-debugging-port=0');
现在就明朗多了,Inspector.targetCrashed这个事件,是由Webkit远程调试协议也就是remote debugging protocol提供的。
Webkit远程调试协议
remote debugging protocol
其定义在webkit的Inspector.json里: Source/WebCore/inspector/Inspector.json#L39-L42
Inspector.json
关于这个event的commit url为:https://github.com/WebKit/webkit/commit/255ba17d1d7e0ad1530d503f28ee5d93d7c5351e#diff-4681ce2c9384e770dfac03ab133f133b
event
现在我们知道了,只要能监听到Inspector.targetCrashed事件,就可以知道网站是否关闭了。我们先在puppeteer的启动参数里,增加一行启动参数:
puppeteer.launch({ '--remote-debugging-port=9222', // other args });
当puppeteer启动时,会监听本地的9222端口,其中路径/json为当前的详情。如:
9222
/json
其格式为:
[ { "description": "", "devtoolsFrontendUrl": "/devtools/inspector.html?ws=127.0.0.1:9222/devtools/page/A1CB5A9CC25A7EE8A99C6A4A1876E4D3", "faviconUrl": "https://s.ytimg.com/yts/img/favicon_32-vflOogEID.png", "id": "A1CB5A9CC25A7EE8A99C6A4A1876E4D3", "title": "張三李四 Chang and Lee 【等無此人 Waiting】 - YouTube", "type": "page", "url": "https://www.youtube.com/watch?v=lAcUGvpRkig&list=PL3p0C_7POnMHG-b0dzkeTVdNuM6yRE5iQ&index=10&t=0s", "webSocketDebuggerUrl": "ws://127.0.0.1:9222/devtools/page/A1CB5A9CC25A7EE8A99C6A4A1876E4D3" }, // other {
其中的type为当前进程的详情:
type
这个type的作用在于,你只想监听某一类型的崩溃。
还有一个更主要的字段:webSocketDebuggerUrl。我们将使用这个字段的值,来进行获取消息。有一个简单的demo:
webSocketDebuggerUrl
const http = require('http'); const WebSocket = require('ws'); http.get('http://127.0.0.1:9222/json', res => { res.addListener('data', data => { const result = JSON.parse(data.toString()); result.forEach(info => { const client = new WebSocket(info.webSocketDebuggerUrl); client.on('message', data => { if (data.indexOf('"method":"Inspector.targetCrashed"') !== -1) { console.error('crash!'); } }); }); }) })
先看懂这段代码,后面的代码才好理解,因为代码过于简单,这里就不再介绍了。
这段代码有个问题是,插件打开网站时,会存在一定的延迟,可能会导致某些网站没有被监听到,而且当这段代码运行后,插件再打开网站时,也不会监听到。针对这个问题,优化了下代码:
const http = require('http'); const WebSocket = require('ws'); module.exports = () => { const wsList = {}; let crashStaus = false; const getWsList = () => { return new Promise((resolve) => { http.get('http://127.0.0.1:9222/json', res => { res.addListener('data', data => { try { const result = JSON.parse(data.toString()); const tempWsList = {}; result.forEach(info => { if (typeof wsList[info.id] === 'undefined') { tempWsList[info.id] = info.webSocketDebuggerUrl; wsList[info.id] = info.webSocketDebuggerUrl; } }); if (Object.keys(tempWsList).length !== 0) { resolve(tempWsList); } } catch (e) { console.error(e); } }); }); }); }; setInterval(() => { getWsList().then(list => { Object.values(list).forEach(wsUrl => { const client = new WebSocket(wsUrl); client.on('message', data => { if (data.indexOf('"method":"Inspector.targetCrashed"') !== -1) { if (!crashStaus) { crashStaus = true; console.log('crash!!!'); } } }); }) }); }, 1000); };
其中需要说明一下这段代码:
if (!crashStaus) { crashStaus = true; console.log('crash!!!'); }
因为我的需求是,任何一个进程crash了,就关闭整个服务,重新运行。所以如果有多个进程同时crash了。我的代码只走一次,不想让他走多次。这个是针对我这里的需求,各位同学可以根据自己的需求更改代码。
Webkit 远程调试协议初探
Chrome 远程调试协议分析与实战
背景介绍
因为正在开发一个项目,而这个项目使用到了
puppeteer
,其中有个功能是在puppeteer
打开的chrome里打开多个Tab
,并进行管理。 虽然puppeteer
可以打开多个网站,但是并不利于管理,所有我使用的是插件的方式,通过插件来打开多网站,并进行管理。但是这里有个需求是,当网站崩溃时,我要做出一些操作。但是目前网上没有一个好的办法去监听当前网站是否崩溃。
可能有同学会说:
puppeteer
不是提供了一个page.on('error', fn)
的方法,来进行监听么?请注意上文中提到的,使用插件打开多个网站,
puppeteer
提供的方法只能对自己打开的网站起作用,没有使用puppeteer
打开的网站,page.on('error', fn)
方法无能为力。使用Service Workers
这个方法是由我同事Haitao提出来的思路。
在当前网站上运行一个
Service Workers
,因为在运行的时候Service Workers
会再启动一个单独的进程,当前网站和Service Workers
是两个单独的进程。也就是说当网站崩溃时,并不影响Service Workers
进程。所以可通过心跳检测
来进行判断网站是否崩溃。网上也有阿里的同学写的相关文章:如何监控网页崩溃?
但是我并没有使用这个方式,因为当
Service Workers
崩溃了,那就没有任何办法了,可能有同学会说:网站和Service Workers
互相发心跳检测
。这可能是一种办法,但是我不太喜欢这种方式。使用Webkit的远程调试协议
介绍
在开始前,我们先去看下
puppeteer
的源码,为什么puppeteer
可以监听到网页的崩溃。其代码在
lib/Page.js
文件里。首先可以看到Page是一个
Class
,其继承了EventEmitter
,EventEmitter
为page
提供了on
方法,也就是我们之前看到的:page.on('error', fn)
从这里就可知,在
Page Class
里,有地方调用了this.emit('error')
来触发error event
。搜了一下,发现其代码在_onTargetCrashed
方法里。如:触发
crash
的方法,我们找到了。那这个_onTargetCrashed
又是在哪触发的呢?可见,是一个叫
client
的方法监听到了Inspector.targetCrashed
事件,而这个事件触发了_onTargetCrashed
函数,clinet
方法就不再跟了,因为跳地方较多,只需要知道,最终client
是一个websocket
的产物。而websocket
创建的代码在lib/Launcher.js
里。代码位置注意这两行:
chromeProcess
是nodejs
中的spawn
产物,代码为:代码位置
其中
chromeArguments
是chrome
启动的参数列表,此列表是有一个--remote-debugging-
的:代码位置
现在就明朗多了,
Inspector.targetCrashed
这个事件,是由Webkit远程调试协议
也就是remote debugging protocol
提供的。其定义在webkit的
Inspector.json
里: Source/WebCore/inspector/Inspector.json#L39-L42关于这个
event
的commit url为:https://github.com/WebKit/webkit/commit/255ba17d1d7e0ad1530d503f28ee5d93d7c5351e#diff-4681ce2c9384e770dfac03ab133f133b编写解决方案代码
现在我们知道了,只要能监听到
Inspector.targetCrashed
事件,就可以知道网站是否关闭了。我们先在puppeteer
的启动参数里,增加一行启动参数:当
puppeteer
启动时,会监听本地的9222
端口,其中路径/json
为当前的详情。如:其格式为:
其中的
type
为当前进程的详情:这个type的作用在于,你只想监听某一类型的崩溃。
还有一个更主要的字段:
webSocketDebuggerUrl
。我们将使用这个字段的值,来进行获取消息。有一个简单的demo:先看懂这段代码,后面的代码才好理解,因为代码过于简单,这里就不再介绍了。
这段代码有个问题是,插件打开网站时,会存在一定的延迟,可能会导致某些网站没有被监听到,而且当这段代码运行后,插件再打开网站时,也不会监听到。针对这个问题,优化了下代码:
其中需要说明一下这段代码:
因为我的需求是,任何一个进程
crash
了,就关闭整个服务,重新运行。所以如果有多个进程同时crash
了。我的代码只走一次,不想让他走多次。这个是针对我这里的需求,各位同学可以根据自己的需求更改代码。参考
Webkit 远程调试协议初探
Chrome 远程调试协议分析与实战