
Electron ClientRequest 类详解基于 Chromium 网络栈发起 HTTP/HTTPS 请求的实现与实战【免费下载链接】electron:electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS项目地址: https://gitcode.com/GitHub_Trending/el/electronClientRequest是 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 请求方法默认GETurlstring可选请求 URL必须为绝对形式协议须为http或httpsheadersRecordstring, string | string[]可选随请求发送的头部sessionSession可选请求关联的Session实例partitionstring可选请求关联的 partition 名称默认空字符串session显式指定时partition被忽略bypassCustomProtocolHandlersboolean可选为true时不触发该 URL scheme 注册的自定义协议处理器可用于将被拦截的请求转发给内置处理器webRequest处理器仍会被触发。默认falsecredentialsstring可选可为include、omit、same-origin决定是否随请求发送凭据见下文详述useSessionCookiesboolean可选是否随请求发送来自所提供 session 的 cookies指定了credentials时此项无效。默认falseprotocolstring可选可为http:或https:默认http:hoststring可选以hostname:port拼接形式提供的服务器主机hostnamestring可选服务器主机名portInteger可选服务器监听端口号pathstring可选请求 URL 的路径部分redirectstring可选重定向模式follow/error/manual默认followoriginstring可选请求的 Origin URLreferrerPolicystring可选可为、no-referrer、no-referrer-when-downgrade、origin、origin-when-cross-origin、unsafe-url、same-origin、strict-origin、strict-origin-when-cross-origin默认strict-origin-when-cross-origincachestring可选可为default、no-store、reload、no-cache、force-cache、only-if-cachedprioritystring可选可为throttled、idle、lowest、low、medium、highest默认idlepriorityIncrementalboolean可选HTTP 可扩展优先级RFC 9218中的增量加载标志默认truecredentials与useSessionCookies的行为细节这是最容易被用错的一组参数设为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 }被列为等价场景印证了“指定credentials后useSessionCookies不再生效”的语义。协议限制与 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); }protocol、host、hostname、port、path等属性严格遵循 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 EventsEvent: responseresponseIncomingMessage — 表示 HTTP 响应消息的对象。Event: loginauthInfoObjectisProxybooleanschemestringhoststringportIntegerrealmstringcallbackFunctionusernamestring可选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事件由底层URLLoader的login事件转发而来若没有任何监听者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: errorerrorError — 提供有关失败信息的错误对象。当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 请求-响应事务中的最后一个事件触发表示request或response对象上不会再发出任何事件。Event: redirectstatusCodeIntegermethodstringredirectUrlstringresponseHeadersRecordstring, string[]当服务器返回重定向响应如 301 Moved Permanently时触发。调用request.followRedirect()会继续重定向。如果处理了这个事件必须同步调用request.followRedirect否则请求将被取消。源码中manual策略的实现精确对应了“同步”这一要求emit(redirect, ...)前后用try/finally包裹事件返回后立即检查标志位未同步跟进就报错销毁请求// lib/common/api/net-client-request.tsredirect 处理节选 } 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默认自动跟随仍会 emitredirect事件供观察此时调用followRedirect()无副作用error请求立即失败错误信息为Attempted to redirect, but redirect policy was errormanual取消重定向除非在redirect事件中同步调用request.followRedirect()实例属性Instance Propertiesrequest.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 Methodsrequest.setHeader(name, value)namestring — 额外的 HTTP 头部名valuestring — 额外的 HTTP 头部值添加一个额外 HTTP 头部。头部名会按原样发出、不做小写化。只能在第一次 write 之前调用之后调用会抛错源码报错文案为Cant set headers after they are sent。若传入的 value 不是string会调用其toString()取得最终值。setHeader前还会经过validateHeader校验name/value 非法时分别抛出Invalid header name/Invalid value for header错误。以下头部不允许应用设置受限头部名单与 Chromium 的 header utils 一致Content-LengthHostTrailer或TeUpgradeCookie2Keep-AliveTransfer-Encoding另外将Connection头部设置为upgrade也不被允许。request.getHeader(name)namestring — 要查询的头部名返回string— 之前设置的头部值内部以头部名小写为键存储。request.removeHeader(name)namestring — 要移除的头部名移除之前设置的额外头部。同样只能在第一次 write 之前调用之后调用会抛错。request.write(chunk[, encoding][, callback])chunk(string | Buffer) — 请求体的一个数据块若是字符串会用指定 encoding 转换为 Bufferencodingstring可选— 用于将字符串块转换为 Buffer默认utf-8callbackFunction可选— 写入操作结束后调用callback本质上是一个为保持与 Node.js API 相似性而引入的占位函数它在 chunk 内容交付给 Chromium 网络层之后的下一个 tick异步调用。与 Node.js 实现不同不保证callback调用时 chunk 内容已经刷到网络上。向请求体添加一个数据块。第一次 write 可能就会把请求头发到网络上第一次 write 之后不允许再添加或移除自定义头部。从源码看非 chunked 模式下第一次write会创建SlurpStream并在finish时统一调用_startRequest()创建底层URLLoaderchunked 模式下则由ChunkedBodyStream在首次写数据时调用_startRequest()开始请求。request.end([chunk][, encoding][, callback])chunk(string | Buffer)可选encodingstring可选callbackFunction可选返回this。发送请求数据的最后一个 chunk。之后不允许再执行 write 或 end 操作。finish事件在 end 操作之后触发。request.abort()取消进行中的 HTTP 事务。如果请求已经发出过close事件abort 操作不产生任何影响否则正在进行的请求会发出abort和close事件。此外若此时存在进行中的 response 对象它将发出aborted事件。实现上abort()会先process.nextTick触发abort事件再标记中止并通过_die()取消底层URLLoader。request.followRedirect()继续等待中的重定向。只能在redirect事件期间调用否则源码会抛出followRedirect() called, but was not waiting for a redirect。request.getUploadProgress()返回Objectactiveboolean — 请求当前是否处于活动状态。若为false其他属性均不会设置startedboolean — 上传是否已开始。若为falsecurrent与total均为 0currentInteger — 目前已上传的字节数totalInteger — 本次请求将上传的总字节数可以与POST请求配合用于获取文件上传或其他数据传输的进度。实现上URLLoader的upload-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().active为falseupload-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的一次请求生命周期可以概括为net.request(options)lib/browser/api/net.ts / lib/utility/api/net.ts→new ClientRequest构造函数中parseOptions完成 URL 拼装、redirect/headers校验首次写入请求体或无 body 的end()触发_startRequest()调用 C 绑定createURLLoader来自process._linkedBinding(electron_common_net)创建底层URLLoaderURLLoader的response-started/data/complete/error/login/redirect/upload-progress事件逐一映射为response事件、IncomingMessage流数据、error/login/redirect/upload-progress事件请求结束时通过_die()销毁流并cancel()底层 loader保证close作为最后事件。C 侧的ElectronURLLoaderFactoryshell/browser/net/electron_url_loader_factory.h进一步处理重定向时的 receiver 绑定等待配合 JS 层的manual策略实现“等followRedirect()才继续”的语义。参考官方文档docs/api/client-request.md、docs/api/net.md、docs/api/incoming-message.mdJS 层实现lib/common/api/net-client-request.ts模块入口lib/browser/api/net.ts、lib/utility/api/net.tsC 网络层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),仅供参考