深入理解 V8 Inspector中幾個(gè)關(guān)鍵的角色
前言:本文介紹一下 V8 關(guān)于 Inspector 的實(shí)現(xiàn),不過不會(huì)涉及到具體命令的實(shí)現(xiàn),V8 Inspector 的命令非常多,了解了處理流程后,如果對(duì)某個(gè)命令感興趣的話,可以單獨(dú)去分析。
首先來看一下 V8 Inspector 中幾個(gè)關(guān)鍵的角色。
V8InspectorSession
- class V8_EXPORT V8InspectorSession {
- public:
- // 收到對(duì)端端消息,調(diào)用這個(gè)方法判斷是否可以分發(fā)
- static bool canDispatchMethod(StringView method);
- // 收到對(duì)端端消息,調(diào)用這個(gè)方法判斷分發(fā)
- virtual void dispatchProtocolMessage(StringView message) = 0;
- };
V8InspectorSession 是一個(gè)基類,本身實(shí)現(xiàn)了 canDispatchMethod 方法,由子類實(shí)現(xiàn) dispatchProtocolMessage 方法。看一下 canDispatchMethod 的實(shí)現(xiàn)。
- bool V8InspectorSession::canDispatchMethod(StringView method) {
- return stringViewStartsWith(method,
- protocol::Runtime::Metainfo::commandPrefix) ||
- stringViewStartsWith(method,
- protocol::Debugger::Metainfo::commandPrefix) ||
- stringViewStartsWith(method,
- protocol::Profiler::Metainfo::commandPrefix) ||
- stringViewStartsWith(
- method, protocol::HeapProfiler::Metainfo::commandPrefix) ||
- stringViewStartsWith(method,
- protocol::Console::Metainfo::commandPrefix) ||
- stringViewStartsWith(method,
- protocol::Schema::Metainfo::commandPrefix);
- }
canDispatchMethod 決定了 V8 目前支持哪些命令。接著看一下 V8InspectorSession 子類的實(shí)現(xiàn)。
- class V8InspectorSessionImpl : public V8InspectorSession,
- public protocol::FrontendChannel {
- public:
- // 靜態(tài)方法,用于創(chuàng)建 V8InspectorSessionImpl
- static std::unique_ptr<V8InspectorSessionImpl> create(V8InspectorImpl*,
- int contextGroupId,
- int sessionId,
- V8Inspector::Channel*,
- StringView state);
- // 實(shí)現(xiàn)命令的分發(fā)
- void dispatchProtocolMessage(StringView message) override;
- // 支持哪些命令
- std::vector<std::unique_ptr<protocol::Schema::API::Domain>> supportedDomains() override;
- private:
- // 發(fā)送消息給對(duì)端
- void SendProtocolResponse(int callId, std::unique_ptr<protocol::Serializable> message) override;
- void SendProtocolNotification(std::unique_ptr<protocol::Serializable> message) override;
- // 會(huì)話 id
- int m_sessionId;
- // 關(guān)聯(lián)的 V8Inspector 對(duì)象
- V8InspectorImpl* m_inspector;
- // 關(guān)聯(lián)的 channel,channel 表示會(huì)話的兩端
- V8Inspector::Channel* m_channel;
- // 處理命令分發(fā)對(duì)象
- protocol::UberDispatcher m_dispatcher;
- // 處理某種命令的代理對(duì)象
- std::unique_ptr<V8RuntimeAgentImpl> m_runtimeAgent;
- std::unique_ptr<V8DebuggerAgentImpl> m_debuggerAgent;
- std::unique_ptr<V8HeapProfilerAgentImpl> m_heapProfilerAgent;
- std::unique_ptr<V8ProfilerAgentImpl> m_profilerAgent;
- std::unique_ptr<V8ConsoleAgentImpl> m_consoleAgent;
- std::unique_ptr<V8SchemaAgentImpl> m_schemaAgent;
- };
下面看一下核心方法的具體實(shí)現(xiàn)。
創(chuàng)建 V8InspectorSessionImpl
- V8InspectorSessionImpl::V8InspectorSessionImpl(V8InspectorImpl* inspector,
- int contextGroupId,
- int sessionId,
- V8Inspector::Channel* channel,
- StringView savedState)
- : m_contextGroupId(contextGroupId),
- m_sessionId(sessionId),
- m_inspector(inspector),
- m_channel(channel),
- m_customObjectFormatterEnabled(false),
- m_dispatcher(this),
- m_state(ParseState(savedState)),
- m_runtimeAgent(nullptr),
- m_debuggerAgent(nullptr),
- m_heapProfilerAgent(nullptr),
- m_profilerAgent(nullptr),
- m_consoleAgent(nullptr),
- m_schemaAgent(nullptr) {
- m_runtimeAgent.reset(new V8RuntimeAgentImpl(this, this, agentState(protocol::Runtime::Metainfo::domainName)));
- protocol::Runtime::Dispatcher::wire(&m_dispatcher, m_runtimeAgent.get());
- m_debuggerAgent.reset(new V8DebuggerAgentImpl(this, this, agentState(protocol::Debugger::Metainfo::domainName)));
- protocol::Debugger::Dispatcher::wire(&m_dispatcher, m_debuggerAgent.get());
- m_profilerAgent.reset(new V8ProfilerAgentImpl(this, this, agentState(protocol::Profiler::Metainfo::domainName)));
- protocol::Profiler::Dispatcher::wire(&m_dispatcher, m_profilerAgent.get());
- m_heapProfilerAgent.reset(new V8HeapProfilerAgentImpl(this, this, agentState(protocol::HeapProfiler::Metainfo::domainName)));
- protocol::HeapProfiler::Dispatcher::wire(&m_dispatcher,m_heapProfilerAgent.get());
- m_consoleAgent.reset(new V8ConsoleAgentImpl(this, this, agentState(protocol::Console::Metainfo::domainName)));
- protocol::Console::Dispatcher::wire(&m_dispatcher, m_consoleAgent.get());
- m_schemaAgent.reset(new V8SchemaAgentImpl(this, this, agentState(protocol::Schema::Metainfo::domainName)));
- protocol::Schema::Dispatcher::wire(&m_dispatcher, m_schemaAgent.get());
- }
V8 支持很多種命令,在創(chuàng)建 V8InspectorSessionImpl 對(duì)象時(shí),會(huì)注冊所有命令和處理該命令的處理器。我們一會(huì)單獨(dú)分析。
接收請(qǐng)求
- void V8InspectorSessionImpl::dispatchProtocolMessage(StringView message) {
- using v8_crdtp::span;
- using v8_crdtp::SpanFrom;
- span<uint8_t> cbor;
- std::vector<uint8_t> converted_cbor;
- if (IsCBORMessage(message)) {
- use_binary_protocol_ = true;
- m_state->setBoolean("use_binary_protocol", true);
- cbor = span<uint8_t>(message.characters8(), message.length());
- } else {
- auto status = ConvertToCBOR(message, &converted_cbor);
- cbor = SpanFrom(converted_cbor);
- }
- v8_crdtp::Dispatchable dispatchable(cbor);
- // 消息分發(fā)
- m_dispatcher.Dispatch(dispatchable).Run();
- }
接收消息后,在內(nèi)部通過 m_dispatcher.Dispatch 進(jìn)行分發(fā),這就好比我們在 Node.js 里收到請(qǐng)求后,根據(jù)路由分發(fā)一樣。具體的分發(fā)邏輯一會(huì)單獨(dú)分析。3. 響應(yīng)請(qǐng)求
- void V8InspectorSessionImpl::SendProtocolResponse(
- int callId, std::unique_ptr<protocol::Serializable> message) {
- m_channel->sendResponse(callId, serializeForFrontend(std::move(message)));
- }
具體的處理邏輯由 channel 實(shí)現(xiàn),channel 由 V8 的使用者實(shí)現(xiàn),比如 Node.js。
數(shù)據(jù)推送
- void V8InspectorSessionImpl::SendProtocolNotification(
- std::unique_ptr<protocol::Serializable> message) {
- m_channel->sendNotification(serializeForFrontend(std::move(message)));
- }
除了一個(gè)請(qǐng)求對(duì)應(yīng)一個(gè)響應(yīng),V8 Inspector 還需要主動(dòng)推送的能力,具體處理邏輯也是由 channel 實(shí)現(xiàn)。從上面點(diǎn)分析可以看到 V8InspectorSessionImpl 的概念相當(dāng)于一個(gè)服務(wù)器,在啟動(dòng)的時(shí)候注冊了一系列路由,當(dāng)建立一個(gè)連接時(shí),就會(huì)創(chuàng)建一個(gè) Channel 對(duì)象表示。調(diào)用方可以通過 Channel 完成請(qǐng)求和接收響應(yīng)。結(jié)構(gòu)如下圖所示。
V8Inspector
- class V8_EXPORT V8Inspector {
- public:
- // 靜態(tài)方法,用于創(chuàng)建 V8Inspector
- static std::unique_ptr<V8Inspector> create(v8::Isolate*, V8InspectorClient*);
- // 用于創(chuàng)建一個(gè) V8InspectorSession
- virtual std::unique_ptr<V8InspectorSession> connect(int contextGroupId,
- Channel*,
- StringView state) = 0;
- };
V8Inspector 是一個(gè)通信的總管,他不負(fù)責(zé)具體的通信,他只是負(fù)責(zé)管理通信者,Channel 才是負(fù)責(zé)通信的角色。下面看一下 V8Inspector 子類的實(shí)現(xiàn) 。
- class V8InspectorImpl : public V8Inspector {
- public:
- V8InspectorImpl(v8::Isolate*, V8InspectorClient*);
- // 創(chuàng)建一個(gè)會(huì)話
- std::unique_ptr<V8InspectorSession> connect(int contextGroupId,
- V8Inspector::Channel*,
- StringView state) override;
- private:
- v8::Isolate* m_isolate;
- // 關(guān)聯(lián)的 V8InspectorClient 對(duì)象,V8InspectorClient 封裝了 V8Inspector,由調(diào)用方實(shí)現(xiàn)
- V8InspectorClient* m_client;
- // 保存所有的會(huì)話
- std::unordered_map<int, std::map<int, V8InspectorSessionImpl*>> m_sessions;
- };
V8InspectorImpl 提供了創(chuàng)建會(huì)話的方法并保存了所有創(chuàng)建的會(huì)話,看一下創(chuàng)建會(huì)話的邏輯。
- std::unique_ptr<V8InspectorSession> V8InspectorImpl::connect(int contextGroupId, V8Inspector::Channel* channel, StringView state) {
- int sessionId = ++m_lastSessionId;
- std::unique_ptr<V8InspectorSessionImpl> session = V8InspectorSessionImpl::create(this, contextGroupId, sessionId, channel, state);
- m_sessions[contextGroupId][sessionId] = session.get();
- return std::move(session);
- }
connect 是創(chuàng)建了一個(gè) V8InspectorSessionImpl 對(duì)象,并通過 id 保存到 map中。結(jié)構(gòu)圖如下。
UberDispatcher
UberDispatcher 是一個(gè)命令分發(fā)器。
- class UberDispatcher {
- public:
- // 表示分發(fā)結(jié)果的對(duì)象
- class DispatchResult {};
- // 分發(fā)處理函數(shù)
- DispatchResult Dispatch(const Dispatchable& dispatchable) const;
- // 注冊命令和處理器
- void WireBackend(span<uint8_t> domain,
- const std::vector<std::pair<span<uint8_t>, span<uint8_t>>>&,
- std::unique_ptr<DomainDispatcher> dispatcher);
- private:
- // 查找命令對(duì)應(yīng)的處理器,Dispatch 中使用
- DomainDispatcher* findDispatcher(span<uint8_t> method);
- // 關(guān)聯(lián)的 channel
- FrontendChannel* const frontend_channel_;
- std::vector<std::pair<span<uint8_t>, span<uint8_t>>> redirects_;
- // 命令處理器隊(duì)列
- std::vector<std::pair<span<uint8_t>, std::unique_ptr<DomainDispatcher>>>
- dispatchers_;
- };
下面看一下注冊和分發(fā)的實(shí)現(xiàn)。
注冊
- void UberDispatcher::WireBackend(span<uint8_t> domain, std::unique_ptr<DomainDispatcher> dispatcher) {
- dispatchers_.insert(dispatchers_.end(), std::make_pair(domain, std::move(dispatcher))););
- }
WireBackend 就是在隊(duì)列里插入一個(gè)新的 domain 和 處理器組合。
分發(fā)命令
- UberDispatcher::DispatchResult UberDispatcher::Dispatch(
- const Dispatchable& dispatchable) const {
- span<uint8_t> method = FindByFirst(redirects_, dispatchable.Method(),
- /*default_value=*/dispatchable.Method());
- // 找到 . 的偏移,命令格式是 A.B
- size_t dot_idx = DotIdx(method);
- // 拿到 domain,即命令的第一部分
- span<uint8_t> domain = method.subspan(0, dot_idx);
- // 拿到命令
- span<uint8_t> command = method.subspan(dot_idx + 1);
- // 通過 domain 查找對(duì)應(yīng)的處理器
- DomainDispatcher* dispatcher = FindByFirst(dispatchers_, domain);
- if (dispatcher) {
- // 交給 domain 對(duì)應(yīng)的處理器繼續(xù)處理
- std::function<void(const Dispatchable&)> dispatched =
- dispatcher->Dispatch(command);
- if (dispatched) {
- return DispatchResult(
- true, [dispatchable, dispatched = std::move(dispatched)]() {
- dispatched(dispatchable);
- });
- }
- }
- }
DomainDispatcher
剛才分析了 UberDispatcher,UberDispatcher 是一個(gè)命令一級(jí)分發(fā)器,因?yàn)槊钍?domain.cmd 的格式,UberDispatcher 是根據(jù) domain 進(jìn)行初步分發(fā),DomainDispatcher 則是找到具體命令對(duì)應(yīng)的處理器。
- class DomainDispatcher {
- // 分發(fā)邏輯,子類實(shí)現(xiàn)
- virtual std::function<void(const Dispatchable&)> Dispatch(span<uint8_t> command_name) = 0;
- // 處理完后響應(yīng)
- void sendResponse(int call_id,
- const DispatchResponse&,
- std::unique_ptr<Serializable> result = nullptr);
- private:
- // 關(guān)聯(lián)的 channel
- FrontendChannel* frontend_channel_;
- };
DomainDispatcher 定義了命令分發(fā)和響應(yīng)的邏輯,不同的 domain 的分發(fā)邏輯會(huì)有不同的實(shí)現(xiàn),但是響應(yīng)邏輯是一樣的,所以基類實(shí)現(xiàn)了。
- void DomainDispatcher::sendResponse(int call_id,
- const DispatchResponse& response,
- std::unique_ptr<Serializable> result) {
- std::unique_ptr<Serializable> serializable;
- if (response.IsError()) {
- serializable = CreateErrorResponse(call_id, response);
- } else {
- serializable = CreateResponse(call_id, std::move(result));
- }
- frontend_channel_->SendProtocolResponse(call_id, std::move(serializable));
- }
通過 frontend_channel_ 返回響應(yīng)。接下來看子類的實(shí)現(xiàn),這里以 HeapProfiler 為例。
- class DomainDispatcherImpl : public protocol::DomainDispatcher {
- public:
- DomainDispatcherImpl(FrontendChannel* frontendChannel, Backend* backend)
- : DomainDispatcher(frontendChannel)
- , m_backend(backend) {}
- ~DomainDispatcherImpl() override { }
- using CallHandler = void (DomainDispatcherImpl::*)(const v8_crdtp::Dispatchable& dispatchable);
- // 分發(fā)的實(shí)現(xiàn)
- std::function<void(const v8_crdtp::Dispatchable&)> Dispatch(v8_crdtp::span<uint8_t> command_name) override;
- // HeapProfiler 支持的命令
- void addInspectedHeapObject(const v8_crdtp::Dispatchable& dispatchable);
- void collectGarbage(const v8_crdtp::Dispatchable& dispatchable);
- void disable(const v8_crdtp::Dispatchable& dispatchable);
- void enable(const v8_crdtp::Dispatchable& dispatchable);
- void getHeapObjectId(const v8_crdtp::Dispatchable& dispatchable);
- void getObjectByHeapObjectId(const v8_crdtp::Dispatchable& dispatchable);
- void getSamplingProfile(const v8_crdtp::Dispatchable& dispatchable);
- void startSampling(const v8_crdtp::Dispatchable& dispatchable);
- void startTrackingHeapObjects(const v8_crdtp::Dispatchable& dispatchable);
- void stopSampling(const v8_crdtp::Dispatchable& dispatchable);
- void stopTrackingHeapObjects(const v8_crdtp::Dispatchable& dispatchable);
- void takeHeapSnapshot(const v8_crdtp::Dispatchable& dispatchable);
- protected:
- Backend* m_backend;
- };
DomainDispatcherImpl 定義了 HeapProfiler 支持的命令,下面分析一下命令的注冊和分發(fā)的處理邏輯。下面是 HeapProfiler 注冊 domain 和 處理器的邏輯(創(chuàng)建 V8InspectorSessionImpl 時(shí))
- // backend 是處理命令的具體對(duì)象,對(duì)于 HeapProfiler domain 是 V8HeapProfilerAgentImpl
- void Dispatcher::wire(UberDispatcher* uber, Backend* backend){
- // channel 是通信的對(duì)端
- auto dispatcher = std::make_unique<DomainDispatcherImpl>(uber->channel(), backend);
- // 注冊 domain 對(duì)應(yīng)的處理器
- uber->WireBackend(v8_crdtp::SpanFrom("HeapProfiler"), std::move(dispatcher));
- }
接下來看一下收到命令時(shí)具體的分發(fā)邏輯。
- std::function<void(const v8_crdtp::Dispatchable&)> DomainDispatcherImpl::Dispatch(v8_crdtp::span<uint8_t> command_name) {
- // 根據(jù)命令查找處理函數(shù)
- CallHandler handler = CommandByName(command_name);
- // 返回個(gè)函數(shù),外層執(zhí)行
- return [this, handler](const v8_crdtp::Dispatchable& dispatchable) {
- (this->*handler)(dispatchable);
- };
- }
看一下查找的邏輯。
- DomainDispatcherImpl::CallHandler CommandByName(v8_crdtp::span<uint8_t> command_name) {
- static auto* commands = [](){
- auto* commands = new std::vector<std::pair<v8_crdtp::span<uint8_t>, DomainDispatcherImpl::CallHandler>>{
- // 太多,不一一列舉
- {
- v8_crdtp::SpanFrom("enable"),
- &DomainDispatcherImpl::enable
- },
- };
- return commands;
- }();
- return v8_crdtp::FindByFirst<DomainDispatcherImpl::CallHandler>(*commands, command_name, nullptr);
- }
再看一下 DomainDispatcherImpl::enable 的實(shí)現(xiàn)。
- void DomainDispatcherImpl::enable(const v8_crdtp::Dispatchable& dispatchable){
- std::unique_ptr<DomainDispatcher::WeakPtr> weak = weakPtr();
- // 調(diào)用 m_backend 也就是 V8HeapProfilerAgentImpl 的 enable
- DispatchResponse response = m_backend->enable();
- if (response.IsFallThrough()) {
- channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("HeapProfiler.enable"), dispatchable.Serialized());
- return;
- }
- if (weak->get())
- weak->get()->sendResponse(dispatchable.CallId(), response);
- return;
- }
DomainDispatcherImpl 只是封裝,具體的命令處理交給 m_backend 所指向的對(duì)象,這里是 V8HeapProfilerAgentImpl。下面是 V8HeapProfilerAgentImpl enable 的實(shí)現(xiàn)。
- Response V8HeapProfilerAgentImpl::enable() {
- m_state->setBoolean(HeapProfilerAgentState::heapProfilerEnabled, true);
- return Response::Success();
- }
結(jié)構(gòu)圖如下。
V8HeapProfilerAgentImpl
剛才分析了 V8HeapProfilerAgentImpl 的 enable 函數(shù),這里以 V8HeapProfilerAgentImpl 為例子分析一下命令處理器類的邏輯。
- class V8HeapProfilerAgentImpl : public protocol::HeapProfiler::Backend {
- public:
- V8HeapProfilerAgentImpl(V8InspectorSessionImpl*, protocol::FrontendChannel*,
- protocol::DictionaryValue* state);
- private:
- V8InspectorSessionImpl* m_session;
- v8::Isolate* m_isolate;
- // protocol::HeapProfiler::Frontend 定義了支持哪些事件
- protocol::HeapProfiler::Frontend m_frontend;
- protocol::DictionaryValue* m_state;
- };
V8HeapProfilerAgentImpl 通過 protocol::HeapProfiler::Frontend 定義了支持的事件,因?yàn)? Inspector 不僅可以處理調(diào)用方發(fā)送的命令,還可以主動(dòng)給調(diào)用方推送消息,這種推送就是以事件的方式觸發(fā)的。
- class Frontend {
- public:
- explicit Frontend(FrontendChannel* frontend_channel) : frontend_channel_(frontend_channel) {}
- void addHeapSnapshotChunk(const String& chunk);
- void heapStatsUpdate(std::unique_ptr<protocol::Array<int>> statsUpdate);
- void lastSeenObjectId(int lastSeenObjectId, double timestamp);
- void reportHeapSnapshotProgress(int done, int total, Maybe<bool> finished = Maybe<bool>());
- void resetProfiles();
- void flush();
- void sendRawNotification(std::unique_ptr<Serializable>);
- private:
- // 指向 V8InspectorSessionImpl 對(duì)象
- FrontendChannel* frontend_channel_;
- };
下面看一下 addHeapSnapshotChunk,這是獲取堆快照時(shí)用到的邏輯。
- void Frontend::addHeapSnapshotChunk(const String& chunk){
- v8_crdtp::ObjectSerializer serializer;
- serializer.AddField(v8_crdtp::MakeSpan("chunk"), chunk);
- frontend_channel_->SendProtocolNotification(v8_crdtp::CreateNotification("HeapProfiler.addHeapSnapshotChunk", serializer.Finish()));
- }
最終觸發(fā)了 HeapProfiler.addHeapSnapshotChunk 事件。另外 V8HeapProfilerAgentImpl 繼承了 Backend 定義了支持哪些請(qǐng)求命令和 DomainDispatcherImpl 中的函數(shù)對(duì)應(yīng),比如獲取堆快照。
- class Backend {
- public:
- virtual ~Backend() { }
- // 不一一列舉
- virtual DispatchResponse takeHeapSnapshot(Maybe<bool> in_reportProgress, Maybe<bool> in_treatGlobalObjectsAsRoots, Maybe<bool> in_captureNumericValue) = 0;
- };
結(jié)構(gòu)圖如下。
Node.js 對(duì) V8 Inspector 的封裝
接下來看一下 Node.js 中是如何使用 V8 Inspector 的,V8 Inspector 的使用方需要實(shí)現(xiàn) V8InspectorClient 和 V8Inspector::Channel。下面看一下 Node.js 的實(shí)現(xiàn)。
- class NodeInspectorClient : public V8InspectorClient {
- public:
- explicit NodeInspectorClient() {
- // 創(chuàng)建一個(gè) V8Inspector
- client_ = V8Inspector::create(env->isolate(), this);
- }
- int connectFrontend(std::unique_ptr<InspectorSessionDelegate> delegate,
- bool prevent_shutdown) {
- int session_id = next_session_id_++;
- channels_[session_id] = std::make_unique<ChannelImpl>(env_,
- client_,
- getWorkerManager(),
- // 收到數(shù)據(jù)后由 delegate 處理
- std::move(delegate),
- getThreadHandle(),
- prevent_shutdown);
- return session_id;
- }
- std::unique_ptr<V8Inspector> client_;
- std::unordered_map<int, std::unique_ptr<ChannelImpl>> channels_;
- };
NodeInspectorClient 封裝了 V8Inspector,并且維護(hù)了多個(gè) channel。Node.js 的上層代碼可以通過 connectFrontend 連接到 V8 Inspector,并拿到 session_id,這個(gè)連接用 ChannelImpl 來實(shí)現(xiàn),來看一下 ChannelImpl 的實(shí)現(xiàn)。
- explicit ChannelImpl(const std::unique_ptr<V8Inspector>& inspector,
- std::unique_ptr<InspectorSessionDelegate> delegate):
- // delegate_ 負(fù)責(zé)處理 V8 發(fā)過來的數(shù)據(jù)
- delegate_(std::move(delegate)) {
- session_ = inspector->connect(CONTEXT_GROUP_ID, this, StringView());
- }
ChannelImpl 是對(duì) V8InspectorSession 的封裝,通過 V8InspectorSession 實(shí)現(xiàn)發(fā)送命令,ChannelImpl 自己實(shí)現(xiàn)了接收響應(yīng)和接收 V8 推送數(shù)據(jù)的邏輯。了解了封裝 V8 Inspector 的能力后,通過一個(gè)例子看一下整個(gè)處理過程。通常我們通過以下方式和 V8 Inspector 通信。
- const { Session } = require('inspector');
- new Session().connect();
我們從 connect 開始分析。
- connect() {
- this[connectionSymbol] = new Connection((message) => this[onMessageSymbol](message));
- }
新建一個(gè) C++ 層的對(duì)象 JSBindingsConnection。
- JSBindingsConnection(Environment* env,
- Local<Object> wrap,
- Local<Function> callback)
- : AsyncWrap(env, wrap, PROVIDER_INSPECTORJSBINDING),
- callback_(env->isolate(), callback) {
- Agent* inspector = env->inspector_agent();
- session_ = LocalConnection::Connect(inspector, std::make_unique<JSBindingsSessionDelegate>(env, this));}static std::unique_ptr<InspectorSession> Connect(
- Agent* inspector, std::unique_ptr<InspectorSessionDelegate> delegate) {
- return inspector->Connect(std::move(delegate), false);
- }
- std::unique_ptr<InspectorSession> Agent::Connect(
- std::unique_ptr<InspectorSessionDelegate> delegate,
- bool prevent_shutdown) {
- int session_id = client_->connectFrontend(std::move(delegate),
- prevent_shutdown);
- return std::unique_ptr<InspectorSession>(
- new SameThreadInspectorSession(session_id, client_));
- }
JSBindingsConnection 初始化時(shí)會(huì)通過 agent->Connect 最終調(diào)用 Agent::Connect 建立到 V8 的通道,并傳入 JSBindingsSessionDelegate 作為數(shù)據(jù)處理的代理(channel 中使用)。最后返回一個(gè) SameThreadInspectorSession 對(duì)象保存到 session_ 中,后續(xù)就可以開始通信了,繼續(xù)看一下 通過 JS 層的 post 發(fā)送請(qǐng)求時(shí)的邏輯。
- post(method, params, callback) {
- const id = this[nextIdSymbol]++;
- const message = { id, method };
- if (params) {
- message.params = params;
- }
- if (callback) {
- this[messageCallbacksSymbol].set(id, callback);
- }
- this[connectionSymbol].dispatch(JSONStringify(message));
- }
為每一個(gè)請(qǐng)求生成一個(gè) id,因?yàn)槭钱惒椒祷氐?,最后調(diào)用 dispatch 函數(shù)。
- static void Dispatch(const FunctionCallbackInfo<Value>& info) {
- Environment* env = Environment::GetCurrent(info);
- JSBindingsConnection* session;
- ASSIGN_OR_RETURN_UNWRAP(&session, info.Holder());
- if (session->session_) {
- session->session_->Dispatch(
- ToProtocolString(env->isolate(), info[0])->string());
- }
- }
看一下 SameThreadInspectorSession::Dispatch (即session->session_->Dispatch)。
- void SameThreadInspectorSession::Dispatch(
- const v8_inspector::StringView& message) {
- auto client = client_.lock();
- if (client)
- client->dispatchMessageFromFrontend(session_id_, message);
- }
SameThreadInspectorSession 中維護(hù)了一個(gè)sessionId,繼續(xù)調(diào)用 client->dispatchMessageFromFrontend, client 是 NodeInspectorClient 對(duì)象。
- void dispatchMessageFromFrontend(int session_id, const StringView& message) {
- channels_[session_id]->dispatchProtocolMessage(message);
- }
dispatchMessageFromFrontend 通過 sessionId 找到對(duì)應(yīng)的 channel。繼續(xù)調(diào) channel 的 dispatchProtocolMessage。
- void dispatchProtocolMessage(const StringView& message) {
- std::string raw_message = protocol::StringUtil::StringViewToUtf8(message);
- std::unique_ptr<protocol::DictionaryValue> value =
- protocol::DictionaryValue::cast(protocol::StringUtil::parseMessage(
- raw_message, false));
- int call_id;
- std::string method;
- node_dispatcher_->parseCommand(value.get(), &call_id, &method);
- if (v8_inspector::V8InspectorSession::canDispatchMethod(
- Utf8ToStringView(method)->string())) {
- session_->dispatchProtocolMessage(message);
- }
- }
最終調(diào)用 V8InspectorSessionImpl 的 session_->dispatchProtocolMessage(message),后面的內(nèi)容前面就講過了,就不再分析。最后看一下數(shù)據(jù)響應(yīng)或者推送時(shí)的邏輯。下面代碼來自 ChannelImpl。
- void sendResponse(
- int callId,
- std::unique_ptr<v8_inspector::StringBuffer> message) override {
- sendMessageToFrontend(message->string());
- }
- void sendNotification(
- std::unique_ptr<v8_inspector::StringBuffer> message) override {
- sendMessageToFrontend(message->string());
- }
- void sendMessageToFrontend(const StringView& message) {
- delegate_->SendMessageToFrontend(message);
- }
我們看到最終調(diào)用了 delegate_->SendMessageToFrontend, delegate 是 JSBindingsSessionDelegate對(duì)象。
- void SendMessageToFrontend(const v8_inspector::StringView& message)
- override {
- Isolate* isolate = env_->isolate();
- HandleScope handle_scope(isolate);
- Context::Scope context_scope(env_->context());
- MaybeLocal<String> v8string = String::NewFromTwoByte(isolate, message.characters16(),
- NewStringType::kNormal, message.length());
- Local<Value> argument = v8string.ToLocalChecked().As<Value>();
- connection_->OnMessage(argument);
- }
接著調(diào)用 connection_->OnMessage(argument),connection 是 JSBindingsConnection 對(duì)象。
- void OnMessage(Local<Value> value) {
- MakeCallback(callback_.Get(env()->isolate()), 1, &value);
- }
C++ 層回調(diào) JS 層。
- [onMessageSymbol](message) {
- const parsed = JSONParse(message);
- try {
- // 通過有沒有 id 判斷是響應(yīng)還是推送
- if (parsed.id) {
- const callback = this[messageCallbacksSymbol].get(parsed.id);
- this[messageCallbacksSymbol].delete(parsed.id);
- if (callback) {
- if (parsed.error) {
- return callback(new ERR_INSPECTOR_COMMAND(parsed.error.code,
- parsed.error.message));
- }
- callback(null, parsed.result);
- }
- } else {
- this.emit(parsed.method, parsed);
- this.emit('inspectorNotification', parsed);
- }
- } catch (error) {
- process.emitWarning(error);
- }
- }
以上就完成了整個(gè)鏈路的分析。整體結(jié)構(gòu)圖如下。
總結(jié)
V8 Inspector 的設(shè)計(jì)和實(shí)現(xiàn)上比較復(fù)雜,對(duì)象間關(guān)系錯(cuò)綜復(fù)雜。因?yàn)?V8 提供調(diào)試和診斷 JS 的文檔似乎不多,也不是很完善,就是簡單描述一下命令是干啥的,很多時(shí)候不一定夠用,了解了具體實(shí)現(xiàn)后,后續(xù)碰到問題,可以自己去看具體實(shí)現(xiàn)。