【问题标题】:Stream large blob file using StreamSaver.js使用 StreamSaver.js 流式传输大型 blob 文件
【发布时间】:2021-08-18 22:44:55
【问题描述】:

我正在尝试使用 Angular 组件中的 StreamSaver.js 将大型数据文件从服务器直接下载到文件系统。但是在 ~2GB 之后会发生错误。似乎数据首先流式传输到浏览器内存中的 blob 中。并且可能存在 2GB 的限制。我的代码基本上取自 StreamSaver 示例。知道我做错了什么以及为什么文件没有直接保存在文件系统上吗?

服务:

public transferData(url: string): Observable<Blob> {
    return this.http.get(url, { responseType: 'blob' });
}

组件:

download(url: string) {
    this.extractionService.transferData(url)
      .subscribe(blob => {
        const fileStream = streamSaver.createWriteStream('data.tel', {
          size: blob.size
        });
        const readableStream = blob.stream();
        if (window.WritableStream && readableStream.pipeTo) {
          return readableStream
            .pipeTo(fileStream)
            .then(() => console.log("done writing"));
        }
        const writer = fileStream.getWriter();
        const reader = readableStream.getReader();
        const pump = () =>
          reader.read()
            .then(res => res.done ? writer.close() : writer.write(res.value).then(pump));
        pump();
      });
}

请求文件的头部:
"内容类型:应用程序/八位字节流\r\n"
"Content-Disposition: attachment; filename=data.tel\r\n"

【问题讨论】:

    标签: javascript angular angular-httpclient


    【解决方案1】:

    建议/背景

    StreamSaver 适用于在客户端生成大量数据的用户,例如长时间的摄像头记录。如果文件来自云端并且您已经有一个Content-Disposition 附件标头,那么您唯一需要做的就是在浏览器中打开此 URL。

    有几种下载文件的方法:

    • location.href = url
    • &lt;a href="url"&gt;download&lt;/a&gt;
    • &lt;iframe src="url" hidden&gt;
    • 对于那些需要发布数据或使用其他 HTTP 方法的人,他们可以发布(隐藏的)&lt;form&gt;

    只要浏览器不知道如何处理文件,它就会触发下载,这就是您已经在使用 Content-Type: application/octet-stream 所做的事情


    由于您使用 Ajax 下载文件并且浏览器知道如何处理数据(将其提供给主 JS 线程),因此Content-TypeContent-Disposition 没有任何用途。

    StreamSaver 尝试模仿服务器如何使用 ServiceWorker 和自定义响应来保存文件。
    您已经在服务器上执行此操作了!您唯一需要做的就是停止使用 AJAX 下载文件。所以我认为你根本不需要 StreamSaver。


    你的问题

    ... 是您首先将整个数据作为 Blob 下载到内存中,然后再保存文件。这违背了使用 StreamSaver 的全部目的,那么您也可以使用更简单的 FileSaver.js 库,或者像 FileSaver.js 一样从 Blob 手动创建对象 url + 链接。

    Object.assign(
      document.createElement('a'), 
      { href: URL.createObjectURL(blob), download: 'name.txt' }
    ).click()
    

    此外,您不能使用 Angular 的 HTTP 服务,因为他们使用旧的 XMLHttpRequest 代替,并且它不能像 fetch 那样从 response.body 为您提供 ReadableStream,所以我的建议是简单地使用取而代之的是 Fetch API。

    https://github.com/angular/angular/issues/36246

    【讨论】:

    • 可能值得一提的是,您是上述 StreamSaver.js 的作者。我认为没有完全披露作者身份(a.k.a spam o_O)的情况下,任何人都不会将此视为自我推销,但最好是安全的,你可能会为我多次称之为“伟大的黑客”而感到自豪;- P
    • 不喜欢吹牛,但是是的,我是 StreamSaver 的作者。我很高兴我被认可c",)。试图通过解释东西的工作原理来写出它更明显是我的意思
    【解决方案2】:

    我以前或多或少处于相同的情况。 Angular http 服务在这个用例中不起作用,因为它没有给你 ReadableStream。我的解决方案是改用fetch API。

    但请注意,fetch流式响应正文是一项实验性功能,不兼容所有浏览器。根据我的测试,它在 Google Chrome 上运行良好,但不适用于 Firefox 或 Safari。为了克服这个限制,我将一个名为 web-streams-polyfill 的 Javascript 库与 fetch 一起使用。

    代码看起来像这样:

    import { WritableStream } from 'web-streams-polyfill/ponyfill';
    import streamSaver from 'streamsaver';
    
    fetch(url, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(data)
    })
    .then(response => {
    
        let contentDisposition = response.headers.get('Content-Disposition');
        let fileName = contentDisposition.substring(contentDisposition.lastIndexOf('=') + 1);
    
        // These code section is adapted from an example of the StreamSaver.js
        // https://jimmywarting.github.io/StreamSaver.js/examples/fetch.html
    
        // If the WritableStream is not available (Firefox, Safari), take it from the ponyfill
        if (!window.WritableStream) {
            streamSaver.WritableStream = WritableStream;
            window.WritableStream = WritableStream;
        }
    
        const fileStream = streamSaver.createWriteStream(fileName);
        const readableStream = response.body;
    
        // More optimized
        if (readableStream.pipeTo) {
            return readableStream.pipeTo(fileStream);
        }
    
        window.writer = fileStream.getWriter();
    
        const reader = response.body.getReader();
        const pump = () => reader.read()
            .then(res => res.done
                ? writer.close()
                : writer.write(res.value).then(pump));
    
        pump();
    })
    .catch(error => {
        console.log(error);
    });;
    

    这个想法是检查window.WritableStream 在当前浏览器中是否可用。如果没有,请将 WritableStreamponyfill 直接分配给 streamSaver.WritableStream 属性。

    由于前段时间遇到这个问题,我的解决方案只在谷歌Chrome 78、Firefox 70、Safari 13上测试过; web-streams-polyfill 2.0.5StreamSaver.js 2.0.3

    【讨论】:

    • 我在使用 Firefox 时无法设置 WritableStream。 import streamSaver from 'streamsaver'; -&gt; has no default export, import * as streamSaver from 'streamsaver'; -> 无法分配给“WritableStream”,因为它是只读属性。我正在使用 StreamSaver.js 2.0.5
    • 它只发生在 Firefox 上吗?谷歌浏览器怎么样?
    • 适用于 Chrome。
    猜你喜欢
    • 2015-12-02
    • 2017-12-11
    • 1970-01-01
    • 2011-10-18
    • 2013-05-27
    • 2021-11-15
    • 2017-11-24
    • 2016-01-02
    相关资源
    最近更新 更多