Electron ResolvedEndpoint 结构与 resolveHost 主机解析:字段语义、完整参数与源码级实现
【免费下载链接】electron:electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS项目地址: https://gitcode.com/GitHub_Trending/el/electron
本文以 Electron 官方 API 文档中的ResolvedEndpoint结构文档为主体,完整解析该结构的字段与取值(含地址族到AF_*常量的映射),说明它在ResolvedHost结果模型中的位置,给出通过net.resolveHost()/ses.resolveHost()获取它的完整参数与用法,并结合仓库中的 C++ 实现与测试用例验证其真实行为。
ResolvedEndpoint 结构:字段与取值
ResolvedEndpoint 结构文档定义了一个描述“单个已解析网络端点”的对象,是 Electron 主机解析结果的最小单元。它包含两个字段:
| 字段 | 类型 | 说明 |
|---|---|---|
address | string | 解析得到的 IP 地址字符串(如10.0.0.1、::1) |
family | string | 地址族,取以下三个枚举值之一 |
family的枚举值直接对应 POSIX socket 编程中的地址族常量:
ipv4— Corresponds toAF_INET;ipv6— Corresponds toAF_INET6;unspec— Corresponds toAF_UNSPEC(地址族未指定)。
这种与系统常量一一对应的命名方式意味着:family不是 Electron 自定义的语义标签,而是底层地址解析栈返回的地址族信息在 JS 层的字符串化表达。对开发者最直接的实践意义是——拿到endpoints数组后,可以用family字段判断每个地址是 IPv4 还是 IPv6,从而在双栈(dual-stack)场景下自行选择连接地址,而不必依赖address字符串本身是否包含:来猜测。
ResolvedEndpoint 在结果模型中的位置:ResolvedHost 容器
ResolvedEndpoint从不单独出现在 API 返回值里,它总是以数组形式内嵌于 ResolvedHost 结构中:
ResolvedHost { endpoints: ResolvedEndpoint[] // resolved DNS entries for the hostname }也就是说,一次主机名解析的结果是“一个主机 → 多个端点”的集合。同一个主机名可以同时拥有 A 记录与 AAAA 记录,或者在 DNS/hosts 中配置了多条记录时返回多个ResolvedEndpoint元素;因此消费端应按数组遍历处理,而不是假设只有一个地址。
获取方式:net.resolveHost() 与 ses.resolveHost()
ResolvedHost(及其内部的ResolvedEndpoint[])由两个 API 以 Promise 形式返回:
net.resolveHost(host, [options])— 基于默认会话解析主机;ses.resolveHost(host, [options])— 基于指定Session解析,便于在自定义会话/分区(不同代理、不同 Cookie 与 DNS 配置)下独立解析。
两个 API 接受完全相同的options参数,完整参数说明如下(继承自 net.md 与 session.md):
hoststring - Hostname to resolve.optionsObject (optional)queryTypestring (optional) - 请求的 DNS 查询类型。未指定时,解析器会根据 IPv4/IPv6 设置自行选择 A 或 AAAA(或两者):A- 仅获取 A 记录;AAAA- 仅获取 AAAA 记录。
sourcestring (optional) - 解析地址的来源。默认允许解析器自行选择合适的来源;该选项只影响“大型外部来源”(如调用系统解析、使用 DNS);即使指定了来源,结果仍可能来自缓存、localhost解析或 IP 字面量。取值:any(默认)- 解析器自行选择,结果可能来自 DNS、MulticastDNS、HOSTS 文件等;system- 结果仅来自系统或操作系统,例如通过getaddrinfo()系统调用;dns- 结果仅来自 DNS 查询;mdns- 结果仅来自 Multicast DNS 查询;localOnly- 不使用任何外部来源,结果仅来自不受 source 设置影响的快速本地来源(缓存、hosts 文件、IP 字面量解析等)。
cacheUsagestring (optional) - 指示可以使用哪些 DNS 缓存条目提供响应:allowed(默认)- 若未过期,结果可来自主机缓存;staleAllowed- 即使过期(按 TTL 或网络变化)也可使用主机缓存;disallowed- 结果不来自主机缓存。
secureDnsPolicystring (optional) - 控制本次请求的 Secure DNS 行为:allow(默认)disable
返回值均为Promise<ResolvedHost>,Promise 以该主机解析出的 IP 地址集合(即endpoints数组)resolve;解析失败时 reject(错误信息为 Chromium 网络错误字符串,如net::ERR_NAME_NOT_RESOLVED)。
典型的 main process 用法:
const { net, session } = require('electron') // 默认会话解析 net.resolveHost('example.com').then(({ endpoints }) => { for (const ep of endpoints) { console.log(ep.family, ep.address) // 例如: ipv4 93.184.216.34 / ipv6 2606:2800:220:1:248:1893:25c8:1946 } }) // 只查 AAAA 记录,强制绕开本地缓存 net.resolveHost('example.com', { queryType: 'AAAA', cacheUsage: 'disallowed' }) .then(({ endpoints }) => console.log(endpoints)) .catch((err) => console.error(err)) // 例如 net::ERR_NAME_NOT_RESOLVED // 在自定义会话中解析(隔离的代理/DNS 环境) session.fromPartition('persist:resolver-test').resolveHost('example.com') .then(({ endpoints }) => console.log(endpoints))源码级实现:从 JS Promise 到 Chromium 主机解析器
从源码结构看,ResolvedEndpoint数组的生成链路横跨 JS 封装层与 C++ 网络层,可以沿以下文件逐层追踪:
1. JS 封装层:net 模块只是 Session 的代理
lib/browser/api/net.ts 中,net.resolveHost直接委托给默认会话:
export function resolveHost(host: string, options?: Electron.ResolveHostOptions): Promise<Electron.ResolvedHost> { return session.defaultSession.resolveHost(host, options) }因此net.resolveHost与ses.resolveHost的差异仅在于使用的Session(即ElectronBrowserContext)不同,核心实现只有一份。
2. C++ 入口:Session::ResolveHost 构造 Promise 与端点字典
真正的 API 绑定在 shell/browser/api/electron_api_session.cc 的Session::ResolveHost中(方法在 L1818 处通过.SetMethod("resolveHost", &Session::ResolveHost)注册)。其关键逻辑:
- 创建
gin_helper::Promise<gin_helper::Dictionary>,并构造一个引用计数的ResolveHostFunction承载本次解析请求; - 回调中先判断网络错误码:若
net_error < 0,则以net::ErrorToString(net_error)作为错误信息 reject —— 这就是文档中“解析失败被 reject 且错误信息形如net::ERR_NAME_NOT_RESOLVED”的来源; - 成功分支中,从 Chromium 的
net::AddressList取出addrs->endpoints(),写入 JS 字典的endpoints键后 resolve:
dict.Set("endpoints", addrs->endpoints()); promise.Resolve(dict);net::AddressList内部的端点集合经 gin 转换器映射为 JS 对象数组,每个对象即一个ResolvedEndpoint,其address与family字段分别对应端点的 IP 地址与地址族——这正是结构文档中family取值能对应AF_INET/AF_INET6/AF_UNSPEC的底层原因。
3. 解析请求的跨进程传递:ResolveHostFunction
shell/browser/net/resolve_host_function.cc(头文件见 resolve_host_function.h)通过 mojo 接口network::mojom::ResolveHostClient向 Chromium 网络服务发起解析。从源码结构看有几个值得注意的细节:
Run()中构造net::HostPortPair(host_, 0)(端口为 0,表明只关心主机解析),并绑定一个 mojo receiver;- 断连即视为解析失败:receiver 的
set_disconnect_handler会调用OnComplete并传入net::ERR_NAME_NOT_RESOLVED与空的AddressList(见 L53-L57)——这解释了为什么测试中“查不到记录”会稳定得到ERR_NAME_NOT_RESOLVED错误; - 进程路径区分(L58-L72):在 utility process 中通过
URLLoaderBundle::GetInstance()->GetHostResolver()发起解析;在浏览器进程中则要求当前处于 UI 线程,走browser_context_->GetDefaultStoragePartition()->GetNetworkContext()->ResolveHost(...)。也就是说,该 API 在主进程与 utility process 中均可用,只是底层的 HostResolver 获取路径不同; OnComplete在回调前显式receiver_.reset()并持有scoped_refptr自引用(见 L75-L88),以保证Reset过程中对象不被提前销毁,最后只把resolve_error_info.error与resolved_addresses传给上层回调。
测试验证:family 与 address 的真实断言
仓库自带测试对ResolvedEndpoint两个字段的取值给出了可复现的验证依据。spec/api-net-spec.ts 中net.resolveHost用例:
test('resolves ipv4.localhost2', async () => { const { endpoints } = await net.resolveHost('ipv4.localhost2') expect(endpoints).to.be.a('array') expect(endpoints).to.have.lengthOf(1) expect(endpoints[0].family).to.equal('ipv4') expect(endpoints[0].address).to.equal('10.0.0.1') })同组测试还断言:ipv6.localhost2解析后endpoints[0].family为'ipv6'、address为'::1';对纯 IPv4 主机请求queryType: 'AAAA'(或对纯 IPv6 主机请求queryType: 'A')、以及解析不存在的notfound.localhost2,都会以/net::ERR_NAME_NOT_RESOLVED/被 reject。spec/api-session-spec.ts 中对ses.resolveHost(host)存在完全平行的用例组,验证了 Session 级 API 与net级 API 返回相同的ResolvedHost/ResolvedEndpoint形态。
这些测试直接印证了结构文档的语义:endpoints是数组,每个元素的family为小写枚举字符串ipv4/ipv6(对应文档中的AF_INET/AF_INET6族),address为点分或冒号记法 IP 字符串。
实践要点小结
- 按数组消费:
endpoints是ResolvedEndpoint[],双栈或一主多名记录会返回多个元素;结合queryType选项(如{ queryType: 'AAAA' })可在解析端就过滤出单一地址族。 - 用 family 而非字符串解析判断地址族:
family与AF_INET/AF_INET6/AF_UNSPEC一一对应,是判断ipv4/ipv6/unspec的可靠依据;unspec表示地址族未指定。 - 选择解析来源与缓存策略:
source控制结果来自 DNS、系统(getaddrinfo())、mDNS 还是仅本地来源;cacheUsage控制是否允许使用主机缓存(含staleAllowed允许过期缓存),排查 DNS 相关问题时可组合source: 'dns'与cacheUsage: 'disallowed'强制走实时 DNS 查询。 - 会话隔离:
net.resolveHost固定使用默认会话;需要不同代理/DNS 环境时,使用ses.resolveHost在对应Session上解析,两者共享同一份 C++ 实现,仅ElectronBrowserContext不同。 - 失败即 reject:解析失败(含网络服务 mojo 管道断连)统一以
net::ERR_NAME_NOT_RESOLVED等 Chromium 网络错误字符串 reject,调用方应始终写catch分支。
【免费下载链接】electron:electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS项目地址: https://gitcode.com/GitHub_Trending/el/electron
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考