Webkit 遠程調試協議初探

jopen 9年前發布 | 21K 次閱讀 WebKit

Webkit 遠程調試協議初探

任何做過 Web 開發的同學,都避免不了在瀏覽器內進行調試。而大部分同學的首選工具,就是 Chrome DevTools。DevTools 本身我們無需多說,是一個大家不能再熟悉的工具了。但是埋藏在 DevTools 下面的開放協議以及它賦予的眾多可能性,至今仍未見到充分的剖析和應用。

Webkit 的遠程調試協議是 Webkit 在 2012 年引入的 。目前所有 Webkit 內核的瀏覽器都支持這一特性。 但是我們還是以 DevTools 和 Chrome 為出發點 ,來做討論。

為什么我們關注 DevTools:

  • 原因 1:DevTools 是開源項目

    DevTools 的源碼就在 Google 的 blink 項目 中,高度的開放。目前這么多豐富的功能,正是 Google 和其社區的共同貢獻。同時它的 License 也不拒絕任何的二次開發。

  • 原因 2:它足夠簡單

    DevTools 僅僅是簡單的由 HTML、JavaScript、CSS、Images 組成的,本質上就是一個 WebApp,純粹的前端應用。當你去了解、修改它時,你不需要理解 C++ 和任何編譯的知識。

  • 原因 3:它的應用架構足夠開放,滿足任何形式的功能擴展。

    事實上 Devtools 是一個充分模塊化的 JavaScript 網頁應用。它的每個功能你都可以去擴展(僅需要了解 JavaScript)。

  • 原因 4:大部分前端都已經習慣它并且喜歡它。

Webkit 的遠程調試特性

談到遠程調試前,有必要先了解各組件之間的關系。

Webkit 遠程調試協議初探

  • 瀏覽器擁有多個 Tab,并為每個 Tab 單獨提供 WebSocket 的 Endpoint URI
  • 每個 DevTool 實例只能檢視一個 Tab,即只能與一個 Tab 保持通訊

DevTools 的界面是數據驅動的。數據的來源就是 WebSocket API。Google 對 Webkit 的調試協議做了進一步的封裝,提供了以 JSON 為序列化格式的 WebSocket 界面。

大家在本地電腦上就可以體驗這個遠程調試是怎樣一回事。執行如下步驟:

  1. 徹底關閉當前 Chrome 進程
  2. 在 Chrome 的啟動參數上加上 --remote-debugging-port=9222 ,例如 Mac 平臺:

    open -a Google\ Chrome –args –remote-debugging-port=9222

  3. 在開啟的 Chrome 瀏覽器里打開任意網頁,例如: http://www.taobao.com/

  4. 在其他瀏覽器或者 Chrome 的新 Tab 打開 http://localhost:9222 ,你會得到這樣的界面:

    Webkit 遠程調試協議初探

  5. 點擊 “淘寶網” 的方框,就進入頁面的調試界面了:

    Webkit 遠程調試協議初探

注意看地址欄,我們訪問的是一個標準的 HTTP 協議下的網頁,不是 Chrome 的私有協議。這里,你可以用 DevTools 再次檢視這個頁面,即按下 CMD + OPTION + i 。你會發現,這真的就是一個 HTML 應用。

再觀察一下這個 URL:

通過 QueryString,我們告訴了 DevTools 的前端應用,它應該連接到哪個 WebSocket 服務。

你可以再你剛打開的檢視 DevTools 的 DevTools(好繞口)里面,觀察整個調試過程中的 WebSocket 通訊。例如:

Webkit 遠程調試協議初探

以前用 WebSocket 做過 RPC 的同學應該看得出來,Google 實現的的確就是一個遠程調用的接口。這個接口里面有兩種通訊模式:

  1. request/response:就如同一個異步調用,通過請求的信息,獲取相應的返回結果。這樣的通訊必然有一個 message id,否則兩方都無法正確的判斷請求和返回的匹配狀況。
  2. notification:和第一種不同,這種模式用于由一方單方面的通知另一方某個信息。和 “事件” 的概念類似。

實驗一:通過調試協議獲取頁面加載的 Timeline 數據

通過調試協議來獲取頁面加載的所有網絡請求并打印。為了簡單,我們編寫一個 Node.js 的應用來實現。大致步驟如下:

  • 用 WebSocket 客戶端連接調試服務
  • 分別監聽 Network.requestWillBeSentNetwork.loadingFailedNetwork.loadingFinishedNetwork.responseReceivedNetwork.requestServedFromCache 的 Notification ,并且打印相關的 log。
  • 發送 Page.navigate 的請求,將頁面跳轉到某個頁面,例如: http://www.taobao.com/

這里拿到的數據足以繪制一個非常準確的頁面加載的瀑布圖。從調試協議里拿到的數據具有以下特點:

  • 準確,這是 Webkit 內核反饋的數據;而不是外層 JavaScript 接口的統計,也不是通過代理監控網絡數據拿到的結果。
  • 豐富,有很多數據,別的方法根本拿不到。例如,緩存狀況、JavaScript 方法執行情況。
  • 標準,調試協議本身已經定義了大量的 JSON 數據結構,你不需要再次進行抽象設計。

完整代碼如下(請先安裝好相應的 npm 模塊,并且打開 Chrome 本地的 9222 調試端口):

var WebSocketClient = require("websocket").client,
    util = require("util"),
    EE = require("events").EventEmitter,
    request = require("request"),
    chalk = require("chalk"),
    exec = require("child_process").exec;


// `Commander` class is message handler that talks to debug service exposed by Chrome
var Commander = function(conn) {
  EE.call(this);
  this.connection = conn;
  this.sendCommands = [];
  var self = this;
  Object.defineProperty(this, "nextMsgId", {
    get: function() {
      return self.sendCommands.length;
    },
    enumerable: true,
    configurable: false
  });
  conn.on("message", this.onMessage.bind(this));
};
util.inherits(Commander, EE);

// Send message using websocket connection
Commander.prototype.send = function(method, params, callback) {
  this.sendCommands.push([method, params, callback]);
  var msg = JSON.stringify({
    id: this.nextMsgId,
    method: method,
    params: params
  });
  console.log(msg);
  this.connection.send(msg);
};


//handler for receiving a message
Commander.prototype.onMessage = function(msg) {
  var command, data;
  if(msg.type === "utf8") {
    data = JSON.parse(msg.utf8Data);
    if(data.id) {//it's method request/response invocation
      command = this.sendCommands[data.id-1];
      if(command) {
        if(command.callback) {
          command.callback(data.params);
        }
      } else {
        console.warn("unmatched message id %s", data.id);
      }
    } else {//notifications
      this.emit(data.method, data.params);
    }
  } else {
    console.warn("message of unknown encoding");
  }
};

//find tab info
request("http://localhost:9222/json", function(e, res, data) {
  data = JSON.parse(data);
  var url = data[0].webSocketDebuggerUrl;
  if(!url) {
    throw new Error("no url");
  }

  var client = new WebSocketClient();

  //once it's connect, start our actions
  client.on("connect", function(conn) {
    console.log("client connceted");
    var commander = new Commander(conn);

    //Shoud enable this freatures
    commander.send("Network.enable",{});
    commander.send("Page.enable",{});

    //Listen to wanted notifications
    commander.on("Network.requestWillBeSent", function(data) {
      console.log("[%s] %s %s: %s", chalk.green(data.timestamp), chalk.blue("WillSend"), data.requestId, data.request.url);
    });
    commander.on("Network.loadingFailed", function(data) {
      console.log("[%s] %s %s", chalk.green(data.timestamp), chalk.red("LoadFail"), data.requestId);
    });
    commander.on("Network.loadingFinished", function(data) {
      console.log("[%s] %s %s", chalk.green(data.timestamp), chalk.magenta("LoadDone"), data.requestId);
    });
    commander.on("Network.responseReceived", function(data) {
      console.log("[%s] %s %s: %s Status %s %s", chalk.cyan(data.timestamp), chalk.red("RespRecv"), data.requestId, data.type, data.response.status, data.response.headers["Content-Length"]);
    });
    commander.on("Network.requestServedFromCache", function(data) {
      console.log("%s %s", chalk.gray(data.timestamp), chalk.red("RespCache"), data.requestId);
    });

    commander.on("Page.domContentEventFired", function() {
      console.log(chalk.bgGreen("OnDOMContentLoad\t\t\t\t\t\t\t\t"));
    });

    commander.on("Page.loadEventFired", function() {
      console.log(chalk.bgCyan("OnLoad\t\t\t\t\t\t\t\t"));
    });

    //Navigate to target page
    commander.send("Page.navigate", {url: "http://www.taobao.com"});

  });

  client.connect(url);

});

運行后的結果如下:

Webkit 遠程調試協議初探

結語

本篇內容僅僅介紹調試協議這個概念,以及它的通訊原理。并且,我們通過一個實驗,來展示這套協議的強大特性。后面,我們還會討論其他瀏覽器的調試協議,以及移動設備的調試。

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!