【问题标题】:Send XMLHttpRequest data in chunks or as ReadableStream to reduce memory usage for large data以块或 ReadableStream 的形式发送 XMLHttpRequest 数据以减少大数据的内存使用
【发布时间】:2020-07-12 10:55:01
【问题描述】:

我一直在尝试使用 JS 的 XMLHttpRequest 类进行文件上传。我最初尝试过这样的事情:

const file = thisFunctionReturnsAFileObject();

const request = new XMLHttpRequest();
request.open('POST', '/upload-file');

const rawFileData = await file.arrayBuffer();

request.send(rawFileData);

上面的代码有效(耶!),并将文件的原始二进制数据发送到我的服务器。

但是......它使用了大量内存(因为整个文件都存储在内存中,而 JS 对内存不是特别友好)......我发现在我的机器上(16GB RAM),我无法发送大于 ~100MB 的文件,因为 JS 会分配太多内存,并且 Chrome 选项卡会因 SIGILL 代码而崩溃。


所以,我认为在这里使用 ReadableStreams 是个好主意。在我的情况下它具有足够好的浏览器兼容性(https://caniuse.com/#search=ReadableStream),并且我的 TypeScript 编译器告诉我 request.send(...) 支持 ReadableStreams(我后来得出的结论是这是错误的)。我最终得到了这样的代码:

const file = thisFunctionReturnsAFileObject();

const request = new XMLHttpRequest();
request.open('POST', '/upload-file');

const fileStream = file.stream();

request.send(fileStream);

但是我的 TypeScript 编译器出卖了我(这很伤人),我在服务器 ಠ_ಠ 上收到了“[object ReadableStream]”。

我还没有过多地探索上述方法,所以我不确定是否有办法做到这一点。我也非常感谢这方面的帮助!


将请求拆分成块是一种最佳解决方案,因为一旦发送了一个块,我们就可以在整个请求被接收之前将其从内存中删除。

我已经搜索和搜索,但还没有找到一种方法来做到这一点(这就是我在这里的原因......)。像这样的伪代码将是最佳的:

const file = thisFunctionReturnsAFileObject();

const request = new XMLHttpRequest();
request.open('POST', '/upload-file');

const fileStream = file.stream();
const fileStreamReader = fileStream.getReader();

const sendNextChunk = async () => {
    const chunk = await fileStreamReader.read();

    if (!chunk.done) { // chunk.done implies that there is no more data to be read
        request.writeToBody(chunk.value); // chunk.value is a Uint8Array
    } else {
        request.end();
        break;
    }
}

sendNextChunk();

我希望此代码以块的形式发送请求,并在发送所有块时结束请求。


我尝试过但没有用的最有用的资源:

Method for streaming data from browser to server via HTTP

没有工作,因为:

  • 我需要解决方案来处理单个请求
  • 我不能使用RTCDataChannel,它必须在一个普通的HTTP请求中(除了XMLHttpRequest还有其他方法吗?)
  • 我需要它在现代 Chrome/Firefox/Edge 等中工作(没有 IE 支持很好)

编辑:我不想使用多部分表单(FormData 类)。我想以块的形式发送从文件流中读取的实际二进制数据。

【问题讨论】:

  • @Kaiido 问题中已经有一个最小的可重现示例(第一个代码块)。如果文件长度大于 ~100MB,Chrome 会崩溃,并且 Chrome 任务管理器会向选项卡显示超过 400MB 的分配内存。另外,我需要做一些操作,所以我不能使用xhr.send(file)。正如我所指定的,我想以块的形式发送 raw data,而不是 File 实例。编辑:imgur.com/a/Fy9nmyK,示例
  • @Kaiido。我知道我在做什么。我认为thisFunctionReturnsAFileObject 非常具有描述性。在图像示例中,我显示的数据大于 超过 100MB(如我指定的那样)。 1E9 字节 大于 100MB。我提供的 File 对象没有任何问题,该函数实际上没有任何问题(正如我指定的“上面的代码有效”)。我不是在寻找 SIGILL 错误的原因或解决方案。我知道并指出这是因为 JS 分配了太多的内存。
  • 似乎错过了 OP 点。他们在询问如何在 整个 过程中执行蒸汽发送操作。 100MB 或 1000MB 无关紧要,除非遇到不同的浏览器或资源限制。
  • “上面的代码有效(耶!),并将文件(小尺寸)的原始二进制数据发送到我的服务器。” - 这似乎是“不是错误”,除非可以指向 File.arrayBuffer 已定义的定义资源以返回文件支持的 ArrayBuffer(因此不会在内存中重复)。在这种情况下,我很乐意提供关于 保证 标准行为的参考,因为如果没有另外指定,优化是一个实现细节。
  • 同样,如果 bug 是特定的内存限制,它不适用于问题核心的 O(N)。限制也可能是操作系统内存分配 - 大小只是不同。在 200MB 上工作,好吗.. 1000MB? 16 GB?更多的?没有蒸汽“错误”只会改变限制因素。

标签: javascript typescript xmlhttprequest streaming


【解决方案1】:

XHR afaik 无法做到这一点。但是更现代的fetch API 确实支持为请求正文传递ReadableStream。在你的情况下:

const file = thisFunctionReturnsAFileObject();

const response = await fetch('/upload-file', {
  method: 'POST',
  body: file.stream(),
});

但是,我不确定whether this will actually use chunked encoding

【讨论】:

  • 获取Request 正文实际上并不支持ReadableStream(在最新的Chrome 和Firefox 中测试过)。和我发布的第二个代码块一样的问题
  • 应该注意的是,即使在最新的 Canary (86) 上,它仍然不可用,甚至在标志后面也不可用。此外,根据当前规范,fetch( url, { method: "POST", body: file.stream() } ) 直接与fetch( url, { method: "POST", body: file } ) 执行完全相同的操作。这里是Blob.stream() algorithmhere is extract body。您的回答可能对其他人有用,但您的示例可能应该稍作修改。
【解决方案2】:

您正面临Chrome bug,他们确实将 256MB 的硬限制设置为可以发送的 ArrayBuffer 的大小。

但无论如何,发送 ArrayBuffer 将create a copy of the data,因此您应该直接将数据作为文件发送,因为这只会read the File 完全像您希望的那样,作为小块的流。

因此,采用您的第一个代码块会给出

const file = thisFunctionReturnsAFileObject();

const request = new XMLHttpRequest();
request.open('POST', '/upload-file');

request.send(file);

Ans 这也适用于 Chrome,即使 Gigs 文件很少。您在这里面临的唯一限制是之前,当您对该文件执行任何处理时。


关于发布 ReadableStreams,这最终会到来,但截至今天 2020 年 7 月 13 日,只有 Chrome has started 致力于其实施,我们网络开发人员仍然无法使用它,specs are still 很难有时会带来一些稳定的东西。
但这对您来说不是问题,因为无论如何您都不会赢得任何东西。发布由静态文件制成的 ReadableStream 是没有用的,fetch 和 xhr 都已经在内部完成了。

【讨论】:

  • 大多数浏览器不会发送文件,而是在您执行 request.send(file) 时发送正文长度为 0 的帖子。
  • @SteveOwensSte 什么? XHR.send(file) is supported 自 IE10 以来的所有人(查看表的“blob 作为发送参数”部分)。如果它对你不起作用,那是因为你搞砸了。
猜你喜欢
  • 2014-04-21
  • 1970-01-01
  • 2013-07-16
  • 1970-01-01
  • 2022-08-06
  • 1970-01-01
  • 2021-01-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多