首页
/ Electron WebSocketOptions 深度解析:net.WebSocket 的六项连接选项及其底层实现

Electron WebSocketOptions 深度解析:net.WebSocket 的六项连接选项及其底层实现

2026-09-06 17:34:41作者:郦嵘贵Just

WebSocketOptions 是 Electron net.WebSocket 构造函数的专属扩展参数对象,用于在主进程中使用 Chromium 原生网络栈建立 WebSocket 连接时控制子协议协商、握手请求头、Origin 身份、Cookie 行为与 Session 归属。本文基于当前仓库中该结构的定义文档与实现源码,逐项讲解这六个字段的语义、默认值与相互优先级,并溯源到 JS 层参数校验(lib/browser/api/net-websocket.ts)与原生层建连逻辑(shell/browser/api/electron_api_web_socket.cc),帮助你在生产环境中正确配置带鉴权的长连接。

一、WebSocketOptions 定位:WHATWG 兼容之上的 Electron 扩展

Electron 提供的 net.WebSocketWHATWG WebSocket 接口的"平替"(drop-in replacement):它运行在主进程,通过 Chromium 网络服务(Network Service)发起连接,因此可以复用系统/Session 代理配置、平台信任库的 TLS 校验以及 Session 级网络策略,而这是 Node.js 侧 ws 库无法获得的。

构造函数签名为 new WebSocket(url[, protocols]),其中第二个参数可以是:

  • 字符串或字符串数组:标准 WHATWG 形式,仅表示子协议;
  • WebSocketOptions 对象:Electron 扩展形式,除携带 protocols 外,还能控制握手行为。

从源码结构看,这一"双形态"参数在 JS 层构造函数中被显式拆解(lib/browser/api/net-websocket.ts#L105-L140):字符串归一为数组、数组逐项 String() 化,对象则整体作为 options 传递,且对象中的 protocols 字段会再次按"字符串或字符串数组"规则解析。因此传对象是标准的严格超集,完全向后兼容。

此外有两个硬前提,源码中均有强制检查:

二、六个字段全解

WebSocketOptions 的完整定义(与 docs/api/structures/web-socket-options.md 一致,JS 侧接口见 lib/browser/api/net-websocket.ts#L43-L50):

字段 类型 必填 默认值 说明
protocols string | string[] 请求的 WebSocket 子协议列表
headers Record<string, string> 附加到打开握手(opening handshake)的额外 HTTP 头
origin string WebSocket URL 的 http(s) 等价源 握手时发送的 Origin 头取值
useSessionCookies boolean false 是否在握手中发送 Session Cookie,并存储握手响应中的 Cookie
session Session 默认 Session 连接关联的 Session 实例
partition string 空字符串(即默认 Session) 连接关联的分区名;提供 session 时该字段被忽略

2.1 protocols:子协议,带严格的 Token 校验

子协议名称必须满足 WHATWG 规范中 HTTP token 语法。JS 层用正则 ^[!#$%&'*+\-.^_|~0-9a-zA-Z]+$对每一项做校验,且不允许重复项,违例直接抛出SyntaxError类型的DOMException`(lib/browser/api/net-websocket.ts#L23-L30#L124-L130):

const { net } = require('electron')

// 合法:标准两参形式
const a = new net.WebSocket('wss://chat.example.com', ['chat', 'superchat'])
// 等价:options 形式
const b = new net.WebSocket('wss://chat.example.com', { protocols: ['chat'] })
// 抛 DOMException:'bad protocol' 含空格
// 抛 DOMException:['a', 'a'] 含重复项

测试用例 spec/api-net-websocket-spec.ts#L62-L68 验证了含空格与重复项均会抛错;#L97-L107 验证了服务端 handleProtocols 选中 chat 后,客户端 ws.protocol 会反映协商结果。

2.2 headers:附加握手请求头,逐项做合法性校验

headers 中的键值对会被追加到 WebSocket 打开握手的 HTTP 请求上(典型用途是携带 Authorization 之类的鉴权头)。原生侧不是无脑透传:每一项都会经过 net::HttpUtil::IsValidHeaderName / IsValidHeaderValue 校验,任一非法即抛出 Invalid header name or valueshell/browser/api/electron_api_web_socket.cc#L120-L131),随后打包为 network::mojom::HttpHeader 传入 CreateWebSocket

测试证据见 spec/api-net-websocket-spec.ts#L314-L324

const ws = new net.WebSocket(url, { headers: { 'X-Custom': 'electron' } })
// 服务端握手请求中可观察到 headers['x-custom'] === 'electron'

2.3 origin:默认取 URL 的 http(s) 等价源

origin 决定握手 Origin 头的值,同时作为请求的发起方(request initiator)参与 SameSite Cookie 匹配。不指定时的默认行为是ws:/wss: 协议改写为 http:/https: 后取源:连接 wss://api.example.com 会发送 Origin: https://api.example.com。这样服务端和 SameSite Cookie 规则都会把该连接当作"同源/first-party"请求对待(shell/browser/api/electron_api_web_socket.cc#L133-L146)。

// 默认:ws://127.0.0.1:3000 → Origin: http://127.0.0.1:3000
const ws = new net.WebSocket('ws://127.0.0.1:3000')
// 显式覆盖
const ws2 = new net.WebSocket(url, { origin: 'https://my-app.example' })

对应测试:spec/api-net-websocket-spec.ts#L326-L348 分别断言了"默认 Origin 等于 ws 地址替换为 http 后的结果"和"显式 origin 原样送达服务端"。

2.4 useSessionCookies:默认 false,显式开启才走 Session Cookie

这是 Electron 特有的安全默认值。从源码看,JS 层将 useSessionCookies 原样透传给原生层(lib/browser/api/net-websocket.ts#L132-L140),原生 Start() 中据此选择 mojom 选项(shell/browser/api/electron_api_web_socket.cc#L193-L210):

  • useSessionCookies: truenetwork::mojom::kWebSocketOptionNone(不拦截 Cookie,配合 2.3 的 first-party Origin,SameSite Cookie 也能随握手发出);
  • 缺省(false)→ network::mojom::kWebSocketOptionBlockAllCookies(完全阻断 Cookie 发送与存储)。

测试 spec/api-net-websocket-spec.ts#L350-L376 给出了完整闭环:

const ses = session.fromPartition(`net-websocket-cookies-${Date.now()}`)
await ses.cookies.set({ url: url.replace('ws://', 'http://'), name: 'ws', value: 'cookie' })

// 开启:服务端可收到 Cookie
new net.WebSocket(url, { session: ses, useSessionCookies: true })  // headers.cookie 包含 'ws=cookie'
// 不开启:即使同一 Session 已写入 Cookie,握手中也不出现 Cookie 头
new net.WebSocket(url, { session: ses })                          // headers.cookie === undefined

需要鉴权状态与 HTTP 请求共享时,才应显式开启;否则默认隔离可避免主进程长连接意外携带浏览器级会话凭据。

2.5 sessionpartition:Session 归属与优先级

这两个字段决定连接挂在哪个 Session(即哪个 BrowserContext 的网络栈、代理、自定义 CA、Host 解析规则下):

  • session:直接传入 Session 实例;
  • partition:分区名,空字符串对应默认 Session;若同时提供 session,则 partition 被忽略

原生层的解析顺序是"session 优先,其次 partition,再退化为默认 Session",解析失败会抛出 Failed to resolve sessionshell/browser/api/electron_api_web_socket.cc#L151-L162):

if (!opts.Get("session", &session)) {
  if (opts.Get("partition", &partition))
    session = Session::FromPartition(args->isolate(), partition);
  else
    session = Session::FromPartition(args->isolate(), "");
}

建连时使用的是该 Session 对应 BrowserContext 默认 Storage Partition 的 NetworkContextshell/browser/api/electron_api_web_socket.cc#L187-L188)。因此"指定独立 partition 的 WebSocket 不受默认 Session 的 setProxy/setCertificateVerifyProc 影响"是可直接依赖的行为。partition 名称的语义与 docs/api/session.mdsession.fromPartition(partition) 一致(区分大小写、支持 persist: 前缀的持久化分区等规则同样适用,具体以该文档为准)。

三、完整数据流:从构造参数到 Network Service

把源码串起来,一次 new net.WebSocket(url, options) 的调用链如下:

  1. JS 构造期lib/browser/api/net-websocket.ts):app.isReady() 检查 → URL 解析与 scheme 归一化(http(s): 改写为 ws(s):,含 fragment 直接抛错)→ 拆解 protocols/options → 调用 process._linkedBinding('electron_common_net').createWebSocket({...})
  2. 原生构造期shell/browser/api/electron_api_web_socket.cc#L98-L171):WebSocketWrapper::Create 校验 URL 与 headers,计算默认 Origin,按 2.5 的优先级解析 Session,通过 cppgc::MakeGarbageCollected 创建包装对象并立即 Start()
  3. 建连期Start()):绑定 handshake mojom pipe 后,向 Session 的 NetworkContext 发起 CreateWebSocket,参数中包含请求方 Origin(net::IsolationInfo::CreateForInternalRequest(origin_))、附加头、Cookie 拦截策略与流量注解;握手失败时 CloseEventcode1006,且 Electron 会把 net::ERR_* 形式的底层网络错误写入 reason,便于免调试器定位问题(docs/api/web-socket.md 的 Events 一节与测试 spec/api-net-websocket-spec.ts#L109-L120 均验证了这一点)。

底层绑定接口的形状见 typings/internal-ambient.d.ts#L124-L147createWebSocket(CreateWebSocketOptions) 返回 WebSocketWrapper,其 open/message/closing/close/error 五个事件正是 JS 层 EventTarget 事件的来源。

四、实战示例:带鉴权头与独立 Session 的连接

综合以上字段,一个典型的主进程鉴权 WebSocket 客户端如下:

const { app, net, session } = require('electron')

app.whenReady().then(() => {
  // 为长连接使用独立分区,避免与默认 Session 共享状态
  const ses = session.fromPartition('ws-secure')

  const ws = new net.WebSocket('wss://api.example.com/stream', {
    protocols: ['v1'],                      // 子协议协商
    headers: { Authorization: `Bearer ${token}` },  // 握手鉴权头
    origin: 'https://api.example.com',      // 与握手目标同源,服务端按同源处理
    useSessionCookies: true,                // 允许发送/存储该 Session 的 Cookie
    session: ses                           // 关联独立 Session(partition 将被忽略)
  })
  ws.binaryType = 'nodebuffer'             // 主进程下默认值,二进制以 Buffer 送达

  ws.onopen = () => ws.send(JSON.stringify({ type: 'subscribe' }))
  ws.onmessage = (e) => { /* e.data 为 string 或 Buffer */ }
  ws.onerror = () => { /* 之后必然跟随 close 事件 */ }
  ws.onclose = (e) => {
    // 连接失败时 e.code === 1006,e.reason 携带 net::ERR_* 描述
    console.log('closed', e.code, e.reason, e.wasClean)
  }
})

几点使用注意(均有仓库依据):

五、小结

WebSocketOptions 六个字段覆盖了主进程 WebSocket 的关键决策点:protocols 决定协商内容,headers/origin 塑造握手的 HTTP 身份,useSessionCookies 控制凭据是否随连接流动,session/partition 决定连接落在哪个网络栈上。其设计取向清晰——与 WHATWG 完全兼容的裸两参形式保持零差异,而 Electron 扩展项全部默认保守(不发 Cookie、Origin 默认 first-party),把风险开关交给调用者显式打开。理解 JS 层校验与原生 CreateWebSocket 的参数映射后,即可在主进程中构建行为可预期、鉴权可控的长连接。

登录后查看全文
热门项目推荐
相关项目推荐