Electron ClientRequest 类详解:基于 Chromium 网络栈发起 HTTP/HTTPS 请求的实现与实战
2026/9/7 10:02:17 网站建设 项目流程

Electron ClientRequest 类详解:基于 Chromium 网络栈发起 HTTP/HTTPS 请求的实现与实战

【免费下载链接】electron:electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS项目地址: https://gitcode.com/GitHub_Trending/el/electron

ClientRequest是 Electron 中用于发起 HTTP/HTTPS 请求的核心类,它是net模块(以及 Utility 进程中的网络 API)的底层返回类型。读完本文,你将完整掌握ClientRequest的全部构造参数(credentials、redirect、priority 等)及其与 Node.jshttp模块的行为差异,并通过 lib/common/api/net-client-request.ts 的源码实现理解请求体缓冲/分块编码、重定向策略与上传进度上报的底层机制,从而在主进程或 Utility 进程中编写健壮的网络请求代码。

定位:ClientRequest在哪里、如何获得

ClientRequest用于发起 HTTP/HTTPS 请求,可运行在主进程(Main)和 Utility 进程(Utility)中。需要特别注意它的使用限制:

该类不从'electron'模块直接导出,只能作为 Electron API 中其他方法的返回值获得——最主要的入口就是 net.request()。

ClientRequest实现了 Node.js 的 Writable Stream 接口,因此本质上也是一个EventEmitter。从源码结构看,它的 TypeScript 实现直接继承自Writable

// lib/common/api/net-client-request.ts export class ClientRequest extends Writable implements Electron.ClientRequest { ... }

主进程与 Utility 进程的net.request都复用同一个ClientRequest实现,区别仅在主进程多了app.isReady()前置校验:

// lib/browser/api/net.ts export function request( options: ClientRequestConstructorOptions | string, callback?: (message: IncomingMessage) => void ) { if (!app.isReady()) { throw new Error('net module can only be used after app is ready'); } return new ClientRequest(options, callback); }

也就是说:ready事件之前调用net.request会直接抛错,Utility 进程则没有这个限制(见 lib/utility/api/net.ts)。

构造函数new ClientRequest(options)

options可以是字符串(按请求 URL 解释),也可以是对象(完整描述一次 HTTP 请求)。若以对象形式给出,支持以下属性:

属性类型说明
methodstring(可选)HTTP 请求方法,默认GET
urlstring(可选)请求 URL,必须为绝对形式,协议须为httphttps
headersRecord<string, string | string[]>(可选)随请求发送的头部
sessionSession(可选)请求关联的Session实例
partitionstring(可选)请求关联的 partition 名称,默认空字符串;session显式指定时partition被忽略
bypassCustomProtocolHandlersboolean(可选)true时不触发该 URL scheme 注册的自定义协议处理器,可用于将被拦截的请求转发给内置处理器;webRequest处理器仍会被触发。默认false
credentialsstring(可选)可为includeomitsame-origin,决定是否随请求发送凭据,见下文详述
useSessionCookiesboolean(可选)是否随请求发送来自所提供 session 的 cookies;指定了credentials时此项无效。默认false
protocolstring(可选)可为http:https:,默认http:
hoststring(可选)hostname:port拼接形式提供的服务器主机
hostnamestring(可选)服务器主机名
portInteger(可选)服务器监听端口号
pathstring(可选)请求 URL 的路径部分
redirectstring(可选)重定向模式:follow/error/manual,默认follow
originstring(可选)请求的 Origin URL
referrerPolicystring(可选)可为""no-referrerno-referrer-when-downgradeoriginorigin-when-cross-originunsafe-urlsame-originstrict-originstrict-origin-when-cross-origin,默认strict-origin-when-cross-origin
cachestring(可选)可为defaultno-storereloadno-cacheforce-cacheonly-if-cached
prioritystring(可选)可为throttledidlelowestlowmediumhighest,默认idle
priorityIncrementalboolean(可选)HTTP 可扩展优先级(RFC 9218)中的增量加载标志,默认true

credentialsuseSessionCookies的行为细节

这是最容易被用错的一组参数:

  • 设为include时,将使用请求关联 session 中的凭据;
  • 设为omit时不发送凭据,遇到 401 时不会触发'login'事件;
  • 设为same-origin必须同时指定origin,否则构造函数直接抛错。源码中的对应校验为:
// lib/common/api/net-client-request.ts if (urlLoaderOptions.credentials === 'same-origin' && !urlLoaderOptions.origin) { throw new Error('credentials: same-origin requires origin to be set'); }
  • 指定credentials,则发送 session 中的认证数据,但不发送 cookies(除非设置了useSessionCookies)。

测试用例 spec/api-net-spec.ts 中也验证了这一优先级关系:{ useSessionCookies: false, credentials: 'include' }{ credentials: 'include' }被列为等价场景,印证了“指定credentialsuseSessionCookies不再生效”的语义。

协议限制与 URL 组装

ClientRequest只支持http:https:两种协议,源码中通过白名单校验:

// lib/common/api/net-client-request.ts const kHttpProtocols = new Set(['http:', 'https:']); ... if (!urlLoaderOptions.allowNonHttpProtocols && !kHttpProtocols.has(urlObj.protocol)) { throw new Error('ClientRequest only supports http: and https: protocols'); }

protocolhosthostnameportpath等属性严格遵循 Node.js URL 模块的模型。等价地,向github.com发起的同一请求可以用两种方式构造:

// 方式一:URL 字符串 const request = net.request('https://github.com/') // 方式二:显式字段(与 Node.js URL 模块模型一致) const request = net.request({ method: 'GET', protocol: 'https:', hostname: 'github.com', port: 443, path: '/' })

解析逻辑位于 lib/common/api/net-client-request.ts 的parseOptions中:字符串参数会被new URL(optionsIn)解析;没有url时则从protocol/host/hostname/port/path拼装,且path中含空格会抛出Request path contains unescaped characters错误。此外redirect取值若不是follow/error/manual之一,同样在构造阶段就抛错。

实例事件(Instance Events)

Event: 'response'

  • responseIncomingMessage — 表示 HTTP 响应消息的对象。

Event: 'login'

  • authInfoObject
    • isProxyboolean
    • schemestring
    • hoststring
    • portInteger
    • realmstring
  • callbackFunction
    • usernamestring(可选)
    • passwordstring(可选)

当需要认证的代理请求用户凭据时触发。callback应携带用户凭据调用:

request.on('login', (authInfo, callback) => { callback('username', 'password') })

提供空凭据将取消该请求,并在响应对象上报告认证错误:

request.on('response', (response) => { console.log(`STATUS: ${response.statusCode}`) response.on('error', (error) => { console.log(`ERROR: ${JSON.stringify(error)}`) }) }) request.on('login', (authInfo, callback) => { callback() })

从源码看,'login'事件由底层URLLoaderlogin事件转发而来;若没有任何监听者,Electron 会主动调用callback()取消认证——这与“空凭据取消请求”的行为一致:

// lib/common/api/net-client-request.ts this._urlLoader.on('login', (event, authInfo, callback) => { const handled = this.emit('login', authInfo, callback); if (!handled) { // If there were no listeners, cancel the authentication request. callback(); } });

Event: 'finish'

request数据的最后一个 chunk 写入request对象之后触发。

Event: 'abort'

request被中止时触发;若request已经关闭(已发出close),则不会触发abort

Event: 'error'

  • errorError — 提供有关失败信息的错误对象。

net模块无法发出网络请求时触发。通常当request对象发出error事件后,会随后跟随一个close事件,并且不会再提供 response 对象。源码中URLLoader的错误会先销毁已存在的响应流,再让request_die(error)(最终destroy(err))路径:

// lib/common/api/net-client-request.ts this._urlLoader.on('error', (event, netErrorString) => { const error = new Error(netErrorString); if (this._response) this._response.destroy(error); this._die(error); });

Event: 'close'

作为 HTTP 请求-响应事务中的最后一个事件触发,表示requestresponse对象上不会再发出任何事件。

Event: 'redirect'

  • statusCodeInteger
  • methodstring
  • redirectUrlstring
  • responseHeadersRecord<string, string[]>

当服务器返回重定向响应(如 301 Moved Permanently)时触发。调用request.followRedirect()会继续重定向。如果处理了这个事件,必须同步调用request.followRedirect,否则请求将被取消。

源码中manual策略的实现精确对应了“同步”这一要求:emit('redirect', ...)前后用try/finally包裹,事件返回后立即检查标志位,未同步跟进就报错销毁请求:

// lib/common/api/net-client-request.ts(redirect 处理节选) } else if (this._redirectPolicy === 'manual') { let _followRedirect = false; this._followRedirectCb = () => { _followRedirect = true; }; try { this.emit('redirect', statusCode, newMethod, newUrl, headers); } finally { this._followRedirectCb = undefined; if (!_followRedirect && !this._aborted) { this._die(new Error('Redirect was cancelled')); } } }

三种redirect策略的行为汇总:

策略遇到重定向时的行为
follow(默认)自动跟随;仍会 emit'redirect'事件供观察,此时调用followRedirect()无副作用
error请求立即失败,错误信息为Attempted to redirect, but redirect policy was 'error'
manual取消重定向,除非在'redirect'事件中同步调用request.followRedirect()

实例属性(Instance Properties)

request.chunkedEncoding

一个boolean,指定请求是否使用 HTTP chunked transfer encoding。默认false。该属性可读可写,但只能在第一次 write 之前设置(此时 HTTP 头部尚未发到网络),第一次 write 之后再设置会抛错。源码中还有一条更严格的约束——该属性只能被设置一次

// lib/common/api/net-client-request.ts set chunkedEncoding(value: boolean) { if (this._started) { throw new Error('chunkedEncoding can only be set before the request is started'); } if (typeof this._chunkedEncoding !== 'undefined') { throw new Error('chunkedEncoding can only be set once'); } ... }

需要发送大请求体时强烈建议使用 chunked 编码:数据会以小块形式流式传输,而不是在 Electron 进程内存中整体缓冲。从源码结构看这一建议的依据非常直接——未开启 chunked 时,请求体写入内部SlurpStream被整体拼接缓存,end之后以完整 Buffer 交给网络层;开启后才走ChunkedBodyStream管道,边写边发:

/** Writable stream that buffers up everything written to it. */ class SlurpStream extends Writable { _write(chunk: Buffer, encoding: string, callback: () => void) { this._data = Buffer.concat([this._data, chunk]); callback(); } ... }

实例方法(Instance Methods)

request.setHeader(name, value)

  • namestring — 额外的 HTTP 头部名
  • valuestring — 额外的 HTTP 头部值

添加一个额外 HTTP 头部。头部名会按原样发出、不做小写化。只能在第一次 write 之前调用,之后调用会抛错(源码报错文案为Can't set headers after they are sent)。若传入的 value 不是string,会调用其toString()取得最终值。setHeader前还会经过validateHeader校验:name/value 非法时分别抛出Invalid header name/Invalid value for header错误。

以下头部不允许应用设置(受限头部名单与 Chromium 的 header utils 一致):

  • Content-Length
  • Host
  • TrailerTe
  • Upgrade
  • Cookie2
  • Keep-Alive
  • Transfer-Encoding

另外,将Connection头部设置为upgrade也不被允许。

request.getHeader(name)

  • namestring — 要查询的头部名

返回string— 之前设置的头部值(内部以头部名小写为键存储)。

request.removeHeader(name)

  • namestring — 要移除的头部名

移除之前设置的额外头部。同样只能在第一次 write 之前调用,之后调用会抛错。

request.write(chunk[, encoding][, callback])

  • chunk(string | Buffer) — 请求体的一个数据块;若是字符串,会用指定 encoding 转换为 Buffer
  • encodingstring(可选)— 用于将字符串块转换为 Buffer,默认'utf-8'
  • callbackFunction(可选)— 写入操作结束后调用

callback本质上是一个为保持与 Node.js API 相似性而引入的占位函数,它在 chunk 内容交付给 Chromium 网络层之后的下一个 tick异步调用。与 Node.js 实现不同,不保证callback调用时 chunk 内容已经刷到网络上。

向请求体添加一个数据块。第一次 write 可能就会把请求头发到网络上;第一次 write 之后不允许再添加或移除自定义头部。从源码看,非 chunked 模式下第一次write会创建SlurpStream并在finish时统一调用_startRequest()创建底层URLLoader;chunked 模式下则由ChunkedBodyStream在首次写数据时调用_startRequest()开始请求。

request.end([chunk][, encoding][, callback])

  • chunk(string | Buffer)(可选)
  • encodingstring(可选)
  • callbackFunction(可选)

返回this

发送请求数据的最后一个 chunk。之后不允许再执行 write 或 end 操作。finish事件在 end 操作之后触发。

request.abort()

取消进行中的 HTTP 事务。如果请求已经发出过close事件,abort 操作不产生任何影响;否则正在进行的请求会发出abortclose事件。此外,若此时存在进行中的 response 对象,它将发出aborted事件。实现上abort()会先process.nextTick触发abort事件,再标记中止并通过_die()取消底层URLLoader

request.followRedirect()

继续等待中的重定向。只能在'redirect'事件期间调用,否则源码会抛出followRedirect() called, but was not waiting for a redirect

request.getUploadProgress()

返回Object

  • activeboolean — 请求当前是否处于活动状态。若为false,其他属性均不会设置
  • startedboolean — 上传是否已开始。若为falsecurrenttotal均为 0
  • currentInteger — 目前已上传的字节数
  • totalInteger — 本次请求将上传的总字节数

可以与POST请求配合,用于获取文件上传或其他数据传输的进度。实现上,URLLoaderupload-progress事件会同时更新内部状态并对外 emit 一个(目前未写入官方文档的)'upload-progress'事件,两者返回值一致:

// lib/common/api/net-client-request.ts this._urlLoader.on('upload-progress', (event, position, total) => { this._uploadProgress = { active: true, started: true, current: position, total }; this.emit('upload-progress', position, total); // Undocumented, for now }); getUploadProgress(): UploadProgress { return this._uploadProgress ? { ...this._uploadProgress } : { active: false, started: false, current: 0, total: 0 }; }

spec/api-net-spec.ts 中的“should report upload progress”测试印证了这一契约:end之前getUploadProgress().activefalseupload-progress事件触发后返回值与事件参数(position, total)完全一致。

完整实战示例:流式上传与进度上报

结合前文要点,下面是一个在主进程中使用net.request的完整示例:大请求体使用 chunked 编码流式发送,并轮询上传进度:

const { app, net } = require('electron') app.whenReady().then(() => { const request = net.request({ method: 'POST', protocol: 'https:', hostname: 'example.com', port: 443, path: '/upload', redirect: 'follow', priority: 'low' }) // 大请求体:先开 chunked,再流式 write request.chunkedEncoding = true request.on('response', (response) => { console.log(`STATUS: ${response.statusCode}`) response.on('data', (chunk) => { console.log(`BODY: ${chunk}`) }) response.on('end', () => { console.log('No more data in response.') }) }) request.on('login', (authInfo, callback) => { callback('username', 'password') // 空凭据则取消请求 }) request.on('error', (err) => console.error(err)) request.on('close', () => console.log('transaction closed')) const timer = setInterval(() => { const p = request.getUploadProgress() console.log(p.active ? `uploaded ${p.current}/${p.total}` : 'inactive') }, 500) request.write(Buffer.from('first-chunk')) request.end(Buffer.from('last-chunk')) request.on('finish', () => clearInterval(timer)) })

需要认证的代理场景中,也可参考 docs/api/net.md 列出的能力:net模块使用 Chromium 原生网络库,自动管理系统代理配置(含 wpad、PAC)、自动隧道化 HTTPS 请求、支持 basic / digest / NTLM / Kerberos / negotiate 等认证方案。

底层调用链速览

综合源码,ClientRequest的一次请求生命周期可以概括为:

  1. net.request(options)(lib/browser/api/net.ts / lib/utility/api/net.ts)→new ClientRequest
  2. 构造函数中parseOptions完成 URL 拼装、redirect/headers校验;
  3. 首次写入请求体(或无 body 的end())触发_startRequest(),调用 C++ 绑定createURLLoader(来自process._linkedBinding('electron_common_net'))创建底层URLLoader
  4. URLLoaderresponse-started/data/complete/error/login/redirect/upload-progress事件逐一映射为response事件、IncomingMessage流数据、error/login/redirect/upload-progress事件;
  5. 请求结束时通过_die()销毁流并cancel()底层 loader,保证close作为最后事件。

C++ 侧的ElectronURLLoaderFactory(shell/browser/net/electron_url_loader_factory.h)进一步处理重定向时的 receiver 绑定等待,配合 JS 层的manual策略实现“等followRedirect()才继续”的语义。

参考

  • 官方文档:docs/api/client-request.md、docs/api/net.md、docs/api/incoming-message.md
  • JS 层实现:lib/common/api/net-client-request.ts
  • 模块入口:lib/browser/api/net.ts、lib/utility/api/net.ts
  • C++ 网络层:shell/browser/net/electron_url_loader_factory.h
  • 行为验证测试:spec/api-net-spec.ts

【免费下载链接】electron:electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS项目地址: https://gitcode.com/GitHub_Trending/el/electron

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询