【发布时间】: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