【问题标题】:How to download a big file directly to the disk, without storing it in RAM of a server and browser?如何将大文件直接下载到磁盘,而不将其存储在服务器和浏览器的 RAM 中?
【发布时间】:2020-10-17 21:04:31
【问题描述】:

我想从我的应用使用 Node.js 和 Express.js 运行的同一台服务器(没有外部云文件存储,也就是本地)实现一个大文件下载(大约 10-1024 Mb)。

我想出了如何做到这一点,方法是将整个文件转换为Blob,通过网络传输,然后为Blob 生成带有window.URL.createObjectURL(…) 的下载链接。只要文件很小,这种方法就可以完美地工作,否则不可能将整个Blob保留在服务器和客户端的RAM中。

我尝试使用 File APIAJAX 实现其他几种方法,但看起来 Chrome 将整个文件加载到 RAM 中,然后才将其转储到磁盘。同样,对于小文件可能还可以,但对于大文件,这不是一种选择。

我最后一次尝试是发送一个基本的Get-request:

const aTag = document.createElement("a");
aTag.href = `/downloadDocument?fileUUID=${fileName}`;
aTag.download = fileName;
aTag.click();

在服务器端:

app.mjs

app.get("/downloadDocument", async (req, res) => {

    req.headers.range = "bytes=0";

    const [urlPrefix, fileUUID] = req.url.split("/downloadDocument?fileUUID=");

    const downloadResult = await StorageDriver.fileDownload(fileUUID, req, res);

});

StorageDriver.mjs

export const fileDownload = async function fileDownload(fileUUID, req, res) {

    //e.g. C:\Users\User\Projects\POC\assets\wanted_file.pdf
    const assetsPath = _resolveAbsoluteAssetsPath(fileUUID);

    const options = {
        dotfiles: "deny",
        headers: {
            "Content-Disposition": "form-data; name=\"files\"",
            "Content-Type": "application/pdf",
            "x-sent": true,
            "x-timestamp": Date.now()
        }
    };

    res.sendFile(assetsPath, options, (err) => {

        if (err) {
            console.log(err);
        } else {
            console.log("Sent");
        }

    });

};

当我单击该链接时,Chrome 会在“下载”中显示该文件,但状态为 失败 - 无文件。下载目标中没有文件。

我的问题:

  1. 为什么在发送Get-request 的情况下我得到Failed - No file
  2. 据我了解,res.sendFile 可以是小文件的正确选择,但对于大文件,最好使用res.write,它可以分成块。是否可以将res.writeGet-request 一起使用?

附:我已经详细说明了这个问题,以使其更加狭窄和清晰。以前这个问题的重点是从 Dropbox 下载一个大文件而不将其存储在 RAM 中,可以找到答案: How to download a big file from Dropbox with Node.js?

【问题讨论】:

    标签: javascript node.js download data-transfer chunking


    【解决方案1】:

    Chrome 无法显示良好的下载进度,因为文件正在后台下载。下载后,会创建文件链接并“单击”以强制 Chrome 显示已下载文件的对话框。

    它可以更容易地完成。您需要创建一个GET 请求并让浏览器下载文件,无需使用ajax。

    app.get("/download", async (req, res, next) => {
      const { fileName } = req.query;
      const downloadResult = await StorageDriver.fileDownload(fileName);
      res.set('Content-Type', 'application/pdf');
      res.send(downloadResult.fileBinary);
    });
    
    function fileDownload(fileName) {
      const a = document.createElement("a");
      a.href = `/download?fileName=${fileName}`;
      a.download = fileName;
      a.click();
    }
    

    【讨论】:

    • 我已经尝试过您的建议,它会下载一个文件,但行为保持不变,首先 Chrome 将整个文件下载到 RAM 中,然后才将其转储到磁盘。也许有一些标题,应该要求浏览器直接将文件下载到磁盘?
    • Here is an examplesendFile,在 res.download 下。由于 abs 路径,第一个链接不起作用,但下一个链接很好。我已将root 更改为options
    • 我的想法用完了。您可以在codesandbox 上隔离您的问题吗?只是一个具有相同逻辑的下载部分。也许我可以帮你调试这部分。
    • 好像和我一样。它们都基于 stream 底层。根据文档,res.write 可以“流式传输”文件,res.sendFile 使用这个名为 send 的包,它基于 fs.createReadStream (source code)。但我不能 100% 确定,也许这些流在某些方面有所不同。
    • 我在 GitHub 上发现了一些问题,但是这些问题与正文大小有关(也就是上传)github.com/expressjs/express/issues/4237stackoverflow.com/questions/13374238/…
    猜你喜欢
    • 2010-10-20
    • 1970-01-01
    • 1970-01-01
    • 2011-01-21
    • 1970-01-01
    • 2018-01-09
    • 2020-06-23
    • 2018-02-01
    • 2017-01-14
    相关资源
    最近更新 更多