由於正在開發一個項目,而這個項目使用到了puppeteer
,其中有個功能是在puppeteer
打開的chrome裏打開多個Tab
,並進行管理。 雖然puppeteer
能夠打開多個網站,可是並不利於管理,全部我使用的是插件的方式,經過插件來打開多網站,並進行管理。html
可是這裏有個需求是,當網站崩潰時,我要作出一些操做。可是目前網上沒有一個好的辦法去監聽當前網站是否崩潰。node
可能有同窗會說:puppeteer
不是提供了一個page.on('error', fn)
的方法,來進行監聽麼?git
請注意上文中提到的,使用插件打開多個網站,puppeteer
提供的方法只能對本身打開的網站起做用,沒有使用puppeteer
打開的網站,page.on('error', fn)
方法無能爲力。github
這個方法是由我同事Haitao提出來的思路。web
在當前網站上運行一個Service Workers
,由於在運行的時候Service Workers
會再啓動一個單獨的進程,當前網站和Service Workers
是兩個單獨的進程。也就是說當網站崩潰時,並不影響Service Workers
進程。因此可經過心跳檢測
來進行判斷網站是否崩潰。chrome
網上也有阿里的同窗寫的相關文章:如何監控網頁崩潰?json
可是我並無使用這個方式,由於當Service Workers
崩潰了,那就沒有任何辦法了,可能有同窗會說:網站和Service Workers
互相發心跳檢測
。這多是一種辦法,可是我不太喜歡這種方式。websocket
在開始前,咱們先去看下puppeteer
的源碼,爲何puppeteer
能夠監聽到網頁的崩潰。socket
其代碼在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
裏。代碼位置
注意這兩行:
const transport = new PipeTransport((chromeProcess.stdio[3]), (chromeProcess.stdio[4]));
connection = new Connection('', transport, slowMo);
複製代碼
chromeProcess
是nodejs
中的spawn
產物,代碼爲:
const chromeProcess = childProcess.spawn(
chromeExecutable,
chromeArguments,
{
detached: process.platform !== 'win32',
env,
stdio
}
);
複製代碼
其中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的Inspector.json
裏: Source/WebCore/inspector/Inspector.json#L39-L42
關於這個event
的commit url爲:github.com/WebKit/webk…
如今咱們知道了,只要能監聽到Inspector.targetCrashed
事件,就能夠知道網站是否關閉了。咱們先在puppeteer
的啓動參數裏,增長一行啓動參數:
puppeteer.launch({
'--remote-debugging-port=9222',
// other args
});
複製代碼
當puppeteer
啓動時,會監聽本地的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的做用在於,你只想監聽某一類型的崩潰。
還有一個更主要的字段:webSocketDebuggerUrl
。咱們將使用這個字段的值,來進行獲取消息。有一個簡單的demo:
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
了。個人代碼只走一次,不想讓他走屢次。這個是針對我這裏的需求,各位同窗能夠根據本身的需求更改代碼。
我司(愛樂奇)招人,感興趣的小夥伴能夠來投簡歷呀。
彈性工做制、每日水果、同事都特別nice、96五、團建、五險一金...
地點上海浦軟大廈