Electron net 模块深度解析:基于 Chromium 原生网络栈的 HTTP、fetch、DNS 解析与 WebSocket 请求实战
net 是 Electron 提供的客户端网络请求 API,它直接使用 Chromium 的原生网络库(而非 Node.js 的 http/https 实现)来发起 HTTP/HTTPS 请求,并额外提供网络在线状态检测、DNS 解析和 WHATWG 兼容的 WebSocket 连接能力。读完本文,你将掌握 net.request、net.fetch、net.isOnline、net.resolveHost 与 net.WebSocket 五个核心成员的完整用法、参数取值与限制,并能结合 Electron 仓库源码理解这些 API 从 JS 层到 Chromium network 组件的完整调用链,以及如何在主进程中正确处理代理、自定义协议与 Cookie 场景。
一、net 模块定位:为什么用 Chromium 网络栈而不是 Node.js 的 http
net 模块可用在主进程与 Utility 进程中(docs/api/net.md 头部声明 Process: Main、Utility)。它的 API 组件——类、方法、属性、事件名——刻意与 Node.js 保持一致,因此从 http.request 迁移过来几乎是无缝的,但底层走的是 Chromium 的网络栈。
官方文档给出的选用 net 而非 Node.js 原生模块的理由(非穷举):
- 自动管理系统代理配置,支持 wpad 协议与 proxy PAC 配置文件;
- HTTPS 请求自动隧道(CONNECT tunneling);
- 支持认证代理,覆盖 basic、digest、NTLM、Kerberos 或 negotiate 认证方案;
- 支持流量监控型代理,即 Fiddler 一类用于访问控制与监控的代理。
最基础的使用方式(官方示例原样保留):
const { app } = require('electron')
app.whenReady().then(() => {
const { net } = require('electron')
const request = net.request('https://github.com')
request.on('response', (response) => {
console.log(`STATUS: ${response.statusCode}`)
console.log(`HEADERS: ${JSON.stringify(response.headers)}`)
response.on('data', (chunk) => {
console.log(`BODY: ${chunk}`)
})
response.on('end', () => {
console.log('No more data in response.')
})
})
request.end()
})
生命周期约束:必须在 app ready 之后使用
文档明确规定:net API 只能在应用发出 ready 事件之后使用,在此之前调用会抛出错误。这一点在源码中得到印证——lib/browser/api/net.ts 中 request() 的第一行检查:
if (!app.isReady()) {
throw new Error('net module can only be used after app is ready')
}
net.WebSocket 的构造函数做了同样的检查(lib/browser/api/net-websocket.ts),所以 net.WebSocket 也必须在 app.whenReady() 之后构造。
二、net.request(options):创建 ClientRequest
签名与行为
net.request(options)
options: ClientRequestConstructorOptions | string
返回: ClientRequest
该方法用提供的 options 创建 ClientRequest 实例,选项直接透传给 ClientRequest 构造函数。根据 options 中指定的协议 scheme,net.request 既可以发起安全(https)请求,也可以发起非安全(http)请求。
从源码看,request() 只是构造了 JS 层的 ClientRequest(lib/browser/api/net.ts),而 ClientRequest 的 JS 实现位于 lib/common/api/net-client-request.ts,其底层真正发起请求的是原生绑定中的 URL Loader——shell/common/api/electron_api_net.cc 中 electron_common_net 绑定注册了 createURLLoader(对应 SimpleURLLoaderWrapper::Create),这就是 Chromium URLLoader 的封装。也就是说,net.request 的请求体数据流最终交给的是 Chromium 的 network::SimpleURLLoader,而不是 Node 的 libuv socket。
相关约束与配套文档
ClientRequest的完整选项(url、method、headers、session、credentials、cache、redirect、partition等)与事件说明见 ClientRequest 文档;- 请求发出前会经过 Chromium 的网络拦截层,主进程注册在 shell/browser/net/electron_url_loader_factory.cc 中的自定义 URL loader factory 会对请求做协议路由与拦截;
- 需要监控、改写或取消请求时,可以配合 webRequest API(
net发起的请求同样会触发 webRequest 处理器,见下文net.fetch一节); - 想抓取完整网络日志进行排查,可使用 netLog 与
session.startNetLog()。
三、net.fetch(input[, init]):主进程里的 fetch()
签名与语义
net.fetch(input, init?)
input: string | GlobalRequest
init: RequestInit & { bypassCustomProtocolHandlers?: boolean } (可选)
返回: Promise<GlobalResponse>
net.fetch 以与渲染进程中 fetch() 相同的方式发送请求,但走 Chromium 网络栈;这与 Node 内置 fetch()(走 Node.js HTTP 栈)不同。官方示例:
async function example () {
const response = await net.fetch('https://my.app')
if (response.ok) {
const body = await response.json()
// ... use the result.
}
}
注意 net.fetch() 默认从 default session 发起请求;要从其他 session 发起,应使用 ses.fetch()。
已声明的限制(Limitations)
文档明确列出三点,使用 net.fetch 时应当知晓:
- 不支持
data:或blob:scheme; integrity选项的值会被忽略;- 返回的
Response对象的.type与.url值不正确。
自定义协议与 bypassCustomProtocolHandlers
这是 net.fetch 最容易被忽视、也最实用的非标准扩展。默认情况下,net.fetch 发出的请求可以打到 自定义协议 以及 file: URL,并会触发已注册的 webRequest 处理器。而在 RequestInit 中设置非标准选项 bypassCustomProtocolHandlers: true 后,该请求不会再调用自定义协议处理器——这使得"在协议处理器里转发请求到内置处理器"成为可能(例如按 URL 区分自研应用内容页与外网请求)。即便绕过自定义协议,webRequest 处理器仍会被触发。
文档给出的官方示例:
protocol.handle('https', (req) => {
if (req.url === 'https://my-app.com') {
return new Response('<body>my app</body>')
} else {
return net.fetch(req, { bypassCustomProtocolHandlers: true })
}
})
注意:在 Utility 进程 中,自定义协议不受支持。
源码视角:net.fetch 是如何实现的
lib/browser/api/net.ts 中 net.fetch 只是一行委托:
export function fetch(input: RequestInfo, init?: RequestInit): Promise<Response> {
return session.defaultSession.fetch(input, init)
}
即 net.fetch 恒等于 session.defaultSession.fetch,这也是"如何换 session"的答案——直接调 ses.fetch(input, init)。
真正的实现体是 lib/browser/api/net-fetch.ts 中的 fetchWithSession。从源码结构看,它的工作方式可以归纳为:
- 构造 Request:用全局
Request包装input/init,构造失败则 Promise 直接 reject;若req.signal已被 abort,按 fetch 规范的 Abort 流程拒绝并取消 body 流(net-fetch.ts#L37-L55); - 转换为 ClientRequest:把
req的 method、url、origin、credentials、cache、referrerPolicy、redirect 组装成ClientRequestConstructorOptions(经由allowAnyProtocol),底层依旧走net.request的 Chromium 链路;bypassCustomProtocolHandlers被写入内部字段_urlLoaderOptions(net-fetch.ts#L87-L100); - Header 与 mode 处理:
credentials: 'same-origin'且无 origin 时会放宽为'include';非 cors 的 mode 通过Sec-Fetch-Mode头传递(net-fetch.ts#L102-L109); - 响应装配:收到
response事件后把 Node 风格的 headers 装入Headers;对 101/204/205/304 与 HEAD 请求返回空 body,否则用Readable.toWeb把响应流包成ReadableStream构造Response(net-fetch.ts#L111-L132)。代码注释还特别说明:protocol.handle会把返回的Response原样转交、不经过 JS 泵流,因此响应上挂了__fetch内部字段保留原始 loader 与流; - 请求体管道:用
Writable.toWeb把ClientRequest(本身是 Writable)转成WritableStream,用req.body.pipeTo(...)流式写入请求体,完成后r.end()(net-fetch.ts#L138-L143)。
由此可以推断:net.fetch 本质是"以 fetch 语义封装的 ClientRequest",请求与响应体均为流式传输,支持 AbortSignal 取消,但受限于底层实现才有前文列出的 data:/blob:、integrity、.type/.url 等限制。
四、net.isOnline() 与 net.online:网络状态检测
net.isOnline() → boolean 当前是否有网络连接
net.online → boolean 只读属性,含义相同
文档对返回值的解读很关键:返回 false 是比较强的"用户无法连接远端站点"的信号;但返回 true 并不能下结论——即便某条链路是通的,也不能保证针对某个特定远端站点的连接尝试会成功。因此在线状态适合做"离线模式入口/提示"级别的判断,不应替代对具体请求成败的监听。
源码上,isOnline 是原生绑定方法,JS 侧只做透传(lib/browser/api/net.ts):
const { isOnline } = process._linkedBinding('electron_common_net')
其实现位于 shell/common/api/electron_api_net.cc:
bool IsOnline() {
return !net::NetworkChangeNotifier::IsOffline();
}
即直接查询 Chromium 的 net::NetworkChangeNotifier。这说明 net.isOnline() 反映的是 Chromium 网络栈对系统网络可达性的判断,与 Node 的 os.networkInterfaces() 或浏览器 navigator.onLine 的实现来源一致。JS 层的 net.online 属性则是每次访问都调用 isOnline() 的 getter(lib/browser/api/net.ts#L31-L33),所以它是"实时"的,缓存其值没有意义。
五、net.resolveHost(host, [options]):DNS 解析
签名与参数
net.resolveHost(host, options?)
host: string 要解析的主机名
options (可选):
queryType: 'A' | 'AAAA' DNS 查询类型;缺省时解析器根据 IPv4/IPv6 配置选择 A、AAAA 或两者
source: 解析结果来源,缺省为 'any'
- 'any' (默认) 解析器自行选择,结果可能来自 DNS、mDNS、HOSTS 文件等
- 'system' 仅来自系统/OS(如 getaddrinfo() 系统调用)
- 'dns' 仅来自 DNS 查询
- 'mdns' 仅来自 Multicast DNS 查询
- 'localOnly' 不使用外部来源,仅来自本地快速来源(缓存、hosts 文件、IP 字面量等)
cacheUsage: 允许使用哪些 DNS 缓存条目
- 'allowed' (默认) 非过期缓存可用
- 'staleAllowed' 过期(过期时间或网络变化)缓存也允许
- 'disallowed' 完全不使用 host 缓存
secureDnsPolicy: 本次请求的 Secure DNS 行为
- 'allow' (默认)
- 'disable'
返回: Promise<ResolvedHost>
返回的 ResolvedHost 结构包含 endpoints: ResolvedEndpoint[],即该主机名解析出的 DNS 条目列表。
与 net.fetch 同理,net.resolveHost 从 default session 发起解析;要针对其他 session 解析,应使用 ses.resolveHost()。
源码佐证
JS 层同样一行委托到 session.defaultSession.resolveHost(lib/browser/api/net.ts#L25-L27)。C++ 侧,resolveHost 作为 electron_common_net 绑定方法注册(shell/common/api/electron_api_net.cc#L92),实际的解析函数实现在 shell/browser/net/resolve_host_function.cc 与 resolve_host_function.h。可以推断它最终调用的是 Chromium net 组件的 HostResolver(source、cacheUsage、secureDnsPolicy 这些参数与 Chromium HostResolver::Options 的语义一一对应),这也是这些参数取值如此"网络组件味"的原因。
典型用途:在网络诊断工具中确认某域名在当前环境下解析到哪些 IP、排查 DNS 污染或劫持、或在应用内实现自己的本地域名映射前先用 localOnly 查看 hosts 文件生效情况。
六、net.WebSocket:主进程的 WHATWG WebSocket
属性说明
net.WebSocket 是 WebSocket 类的构造器引用,仅在主进程可用(Utility 进程中不可用)。它用于通过 Chromium 网络栈创建 WHATWG 兼容 的 WebSocket 连接。官方示例:
const { app, net } = require('electron')
app.whenReady().then(() => {
const ws = new net.WebSocket('wss://echo.websocket.events')
ws.onmessage = (event) => console.log(event.data)
})
完整接口(事件、属性、options 如 session、partition、useSessionCookies、自定义 headers 等)见 WebSocket 文档。
实现细节:JS 封装 + 原生 wrapper
与 net.request/net.fetch 不同,net.WebSocket 的 JS 实现是 Electron 自己写的一个较完整的 WHATWG WebSocket 模拟类,位于 lib/browser/api/net-websocket.ts,底层依赖原生绑定 electron_common_net 的 createWebSocket(C++ 实现为 shell/browser/api/electron_api_web_socket.cc 中的 WebSocketWrapper)。几个从源码可确认的设计点:
- URL 规范化:按 WHATWG WebSocket 规范解析 URL,
http:自动改写为ws:、https:改写为wss:;拒绝携带 fragment 的 URL(net-websocket.ts#L85-L103); - subprotocol 校验:按 RFC 7230 token 规则校验 subprotocol 名称并去重,非法则抛
SyntaxError(net-websocket.ts#L23-L25、L124-L130); - binaryType 支持三种取值:
'blob' | 'arraybuffer' | 'nodebuffer',其中nodebuffer(默认)直接暴露 NodeBuffer,方便主进程做二进制处理(net-websocket.ts#L150-L170); - 有序发送队列:
Blob需要异步读取,源码用一条 Promise 发送队列序列化出站写入,保证后发字符串/ArrayBuffer 不会"超车"先发 Blob;队列中的字节计入bufferedAmount(net-websocket.ts#L224-L273); - close 语义与规范一致:close code 必须为 1000 或 3000–4999,reason 不超过 123 个 UTF-8 字节(net-websocket.ts#L275-L296)。
验证
针对 net.WebSocket 的测试位于 spec/api-web-socket-spec.ts,覆盖消息收发、subprotocol、close code 校验等场景,可作为行为符合性的重要参考。
七、实用建议与常见坑
结合文档与源码,实际使用 net 模块时建议:
- 一切
net调用放在app.whenReady()之后,net.request与net.WebSocket都会在 ready 之前抛错,这是 JS 层显式检查(见 lib/browser/api/net.ts#L15-L17); - 代理与多 session 场景优先用 session 级 API:
net.fetch/net.resolveHost固定走 default session,需要按分区隔离 Cookie/缓存/代理时改用ses.fetch()/ses.resolveHost(); net.isOnline()只作弱信号:true不代表具体请求必成功,false才可较放心地判定离线;- 主进程需要 WebSocket 时用
net.WebSocket,而不是 Node 的ws包或 Node 18+ 内置 WebSocket,前者走 Chromium 网络栈,能享受系统代理配置,并支持自定义session/partition; - 协议处理器内部转发自请求时务必带上
bypassCustomProtocolHandlers: true,否则会造成对protocol.handle的无限递归(文档第三节示例即此用途); - 排查网络问题:配合 webRequest 观察/改写请求、netLog 抓取请求日志、session 设置
resolveHost级别的诊断。
参考文件
- API 文档:net、ClientRequest、WebSocket、session、protocol、webRequest、netLog、ResolvedHost 结构
- JS 实现:net 入口、fetch 实现、WebSocket 实现、ClientRequest 公共实现
- C++ 实现:electron_api_net.cc、resolve_host_function.cc、electron_api_web_socket.cc、自定义 URL loader factory
- 测试:spec/api-net-spec.ts、spec/api-net-custom-protocols-spec.ts、spec/api-web-socket-spec.ts
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00