Electron 中 UploadData 对象详解:请求上传体的结构定义、API 使用与源码实现
Electron 的 UploadData 对象是网络栈向 JS 层暴露"正在上传的请求体"的标准数据结构,主要出现在 webRequest 事件细节与协议处理相关的结构中。读懂它的三个字段、弄清楚它在 session.webRequest 监听器中的出现位置,并对照 源码转换器 了解 Chromium 底层四种上传元素是如何映射成 JS 对象的,你就能在拦截请求时准确识别普通字节、磁盘文件、Blob 和流式上传这四类载荷。
UploadData 对象:官方定义与字段语义
UploadData 官方结构定义 非常紧凑,全文只有三个字段,这里完整继承并展开说明:
| 字段 | 类型 | 是否可选 | 说明 |
|---|---|---|---|
bytes |
Buffer | 必填 | 正在发送的内容(Content being sent) |
file |
string | 可选 | 正在上传的文件路径(Path of file being uploaded) |
blobUUID |
string | 可选 | Blob 数据的 UUID,配合 ses.getBlobData 方法取回实际数据 |
三个字段分别对应三种典型的上传场景:
bytes:请求体在内存中、可以直接以Buffer形式读取时(例如fetch上传字符串/ArrayBuffer),转换器会把底层字节复制成 JS Buffer 挂到该字段上;file:上传的是一段磁盘文件(例如multipart/form-data中通过File从fs路径构造的部分)时,JS 层拿到的只是文件路径字符串,文件内容并不会被整体拷贝进内存;blobUUID:上传数据来自 Blob(DataPipe 形式)时,对象上只携带一个 UUID 标识符,需要通过session实例的ses.getBlobData(identifier)方法按标识取回数据——这一点在官方文档中作为blobUUID字段的说明被明确写出。
UploadData 出现的位置
webRequest 事件中的 details.uploadData
WebRequest 模块文档 中,uploadData 以 UploadData[] 数组形式出现在两个监听器的 details 对象里:
webRequest.onBeforeRequest([filter, ]listener):details.uploadData为[UploadData[]](docs/api/structures/upload-data.md)(非可选),文档原文明确写道 "The uploadDatais an array ofUploadDataobjects"。该事件在请求即将发出时触发,监听器必须通过callback返回一个含cancel、redirectURL等可选字段的 response 对象。webRequest.onBeforeSendHeaders([filter, ]listener):details.uploadData为[UploadData[]](docs/api/structures/upload-data.md)(可选,因为并非所有请求都带上传体),此时details中还会附带requestHeaders,适合在发头阶段检查上传内容并改写请求头。
两个监听器都支持通过 WebRequestFilter 按 URL 过滤,文档给出的合法 URL 模式 包括:
'<all_urls>'
'http://foo:1234/'
'http://foo.com/'
'*://*/*'
'*://example.com/*'
'http://*.foo:1234/'
'file://foo:1234/bar'
ProtocolRequest 结构中的可选 uploadData
ProtocolRequest 结构 定义了描述一次请求的对象:url、referrer、method、headers,以及可选的 uploadData 字段,其类型同样是 UploadData[]。也就是说,在协议处理的请求描述中,上传体同样以 UploadData 数组的形式暴露给嵌入方。
源码实现:Chromium 上传数据如何变成 UploadData
从源码结构看,UploadData 并不是 Electron 自己构造的数据,而是 Chromium 网络栈请求体对象的 JS 投影。关键转换逻辑位于 Converternetwork::ResourceRequestBody::ToV8:它遍历底层 network::ResourceRequestBody 的 elements(),按 DataElement::Tag 枚举逐一分发,构造出 JS 对象数组。四种底层元素到 JS 字段的映射关系如下(见 net_converter.cc):
| 底层 mojom 元素 | type 字段 |
写入的 JS 属性 |
|---|---|---|
kFile(DataElementFile) |
file |
file、filePath、offset、length、modificationTime |
kBytes(DataElementBytes) |
rawData |
bytes(通过 electron::Buffer::Copy 拷贝字节) |
kDataPipe(Blob) |
blob |
blobUUID、dataPipe |
kChunkedDataPipe(流) |
stream |
body(ReadableStream 包装) |
可以推断出几个文档未展开、但源码中明确存在的事实:
- 文档字段是"简化视图"。官方结构文档只列了
bytes/file/blobUUID三个字段,而转换器实际还会写入type以及文件元素的filePath、offset、length、modificationTime。type字段(取值file/rawData/blob/stream)可以看作区分四种载荷的判断依据。 - 字节拷贝发生在转换时。
kBytes分支调用electron::Buffer::Copy,意味着bytesBuffer 是底层数据的一份副本;而kFile分支只传递路径,不触发内容读取,对大文件上传的拦截检查(只读路径、校验文件名等)是零拷贝的。 - Blob 的数据管道生命周期绑定在 UploadData 对象上。源码注释写道 "The lifetime of data pipe is bound to the uploadData object",即
dataPipe属性(DataPipeHolder)与uploadData对象同生命周期——如果你要异步处理 Blob 数据,需要保证持有该uploadData对象或及时用getBlobData取回内容。 getBlobData有被重构的意图。源码中的 TODO 注释 表明:在 NetworkService 重构之后,旧的blobUUIDAPI 变得"不必要地复杂",未来计划弃用getBlobData并直接返回DataPipeHolder包装器。因此在使用blobUUID时宜将其视为稳定的当前 API,但不必为它设计过度复杂的持久化方案。
通过 getBlobData 取回 Blob 内容
对携带 blobUUID 的 UploadData,官方文档给出的取数方式是 ses.getBlobData(identifier),其定义位于 Session API 文档。典型的使用链路是:webRequest 监听器拿到 details.uploadData 中某个元素的 blobUUID → 调用对应 session 的 getBlobData → 按 UUID 换回 Blob 数据。对于 file 元素,则直接根据 file 路径用 fs API 处理即可。
与相近结构的区分
仓库中还有几个容易与 UploadData 混淆的结构,注意它们的适用场景不同:
- UploadRawData / UploadFile:
{type: 'rawData', bytes}与{type: 'file', filePath, offset, length, modificationTime}两种结构,是 PostBody 对象 中data数组的元素类型。PostBody(含contentType、boundary,contentType只允许application/x-www-form-urlencoded或multipart/form-data,对应 HTML 表单的enctype)用于表单提交场景,与UploadData的网络栈来源是两条不同的暴露路径; - ProtocolResponseUploadData:只有
contentType(MIME 类型)与data(string | Buffer)两个字段,出现在 ProtocolResponse 中,属于协议处理器侧的上传响应数据,与请求拦截侧的UploadData不是一回事。
实战示例:在 webRequest 中检查上传内容
结合上述结构定义,一个合法的拦截检查逻辑如下(字段取值与 UploadData 定义、webRequest 事件细节 完全对应):
const { session } = require('electron')
const ses = session.defaultSession
ses.webRequest.onBeforeRequest(
{ urls: ['*://*/*'] },
(details, callback) => {
for (const chunk of details.uploadData) {
if (chunk.file) {
// 磁盘文件上传:只拿到路径,按需再读文件
console.log('file upload:', chunk.file)
} else if (chunk.blobUUID) {
// Blob 上传:用 UUID 异步取回数据
ses.getBlobData(chunk.blobUUID).then(data => {
console.log('blob data size:', data.length)
})
} else if (chunk.bytes) {
// 内存中的原始字节
console.log('raw bytes:', chunk.bytes.length)
}
}
callback({})
}
)
注意:onBeforeRequest 中 uploadData 是必填字段,而 onBeforeSendHeaders 中它是可选的,后者使用时应先做空值判断。
小结
UploadData由bytes(Buffer,必填)、file(string,可选)、blobUUID(string,可选)三字段组成,分别覆盖内存字节、磁盘文件、Blob 三类上传内容;- 它主要出现在
webRequest的onBeforeRequest/onBeforeSendHeaders细节与ProtocolRequest结构中,以数组形式暴露; - 从源码看,它由 net_converter.cc 将 Chromium 的
ResourceRequestBody四种DataElement(file / rawData / blob / stream)转换而来,实际对象上还带有type等文档未列出的字段; - 取回 Blob 数据使用
ses.getBlobData(identifier);与PostBody、ProtocolResponseUploadData等相似结构应明确区分场景后再使用。
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 StartedRust0624
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