使用 StreamSaver.js 流式传输大型 blob 文件
Stream large blob file using StreamSaver.js
我正在尝试使用 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();
});
}
请求文件的header:
"Content-Type: application/octet-stream\r\n"
"Content-Disposition:附件;文件名=data.tel\r\n"
建议/背景
StreamSaver 适用于那些在客户端生成大量数据的用户,例如长时间的摄像机记录。如果文件来自云端并且您已经有一个 Content-Disposition
附件 header 那么您唯一需要做的就是在浏览器中打开这个 URL。
下载文件有以下几种方式:
location.href = url
<a href="url">download</a>
<iframe src="url" hidden>
- 对于那些需要 post 数据或使用其他 HTTP 方法的人,他们可以 post 一个(隐藏)
<form>
代替。
只要浏览器不知道如何处理文件,它就会触发下载,这就是您已经在使用 Content-Type: application/octet-stream
做的事情
由于您使用 Ajax 下载文件并且浏览器知道如何处理数据(将其提供给主 JS 线程),因此 Content-Type
和 Content-Disposition
不会服务于任何目的。
StreamSaver 试图模仿服务器如何使用 ServiceWorkers 和自定义响应保存文件。
您已经在服务器上这样做了!您唯一需要做的就是停止使用 AJAX 下载文件。所以我认为您根本不需要 StreamSaver。
你的问题
... 是先将整个数据作为 Blob 下载到内存中,然后再保存文件。这违背了使用 StreamSaver 的全部目的,那么您也可以使用更简单的 FileSaver.js 库或手动创建 object url + link 从像 FileSaver.js 确实如此。
Object.assign(
document.createElement('a'),
{ href: URL.createObjectURL(blob), download: 'name.txt' }
).click()
此外,您不能使用 Angular 的 HTTP 服务,因为他们使用旧的 XMLHttpRequest
,并且它不能像 fetch
那样为您提供 ReadableStream来自 response.body
所以我的建议是简单地使用 Fetch API 代替。
我以前或多或少遇到过同样的情况。 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
在当前浏览器中是否可用。如果不是,则将 ponyfill
中的 WritableStream
直接分配给 streamSaver.WritableStream
属性.
由于前段时间遇到这个问题,所以我的解决方案只在Google Chrome 78、Firefox 70、Safari 13上进行了测试; web-streams-polyfill 2.0.5, and StreamSaver.js 2.0.3
我正在尝试使用 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();
});
}
请求文件的header:
"Content-Type: application/octet-stream\r\n"
"Content-Disposition:附件;文件名=data.tel\r\n"
建议/背景
StreamSaver 适用于那些在客户端生成大量数据的用户,例如长时间的摄像机记录。如果文件来自云端并且您已经有一个 Content-Disposition
附件 header 那么您唯一需要做的就是在浏览器中打开这个 URL。
下载文件有以下几种方式:
location.href = url
<a href="url">download</a>
<iframe src="url" hidden>
- 对于那些需要 post 数据或使用其他 HTTP 方法的人,他们可以 post 一个(隐藏)
<form>
代替。
只要浏览器不知道如何处理文件,它就会触发下载,这就是您已经在使用 Content-Type: application/octet-stream
由于您使用 Ajax 下载文件并且浏览器知道如何处理数据(将其提供给主 JS 线程),因此 Content-Type
和 Content-Disposition
不会服务于任何目的。
StreamSaver 试图模仿服务器如何使用 ServiceWorkers 和自定义响应保存文件。
您已经在服务器上这样做了!您唯一需要做的就是停止使用 AJAX 下载文件。所以我认为您根本不需要 StreamSaver。
你的问题
... 是先将整个数据作为 Blob 下载到内存中,然后再保存文件。这违背了使用 StreamSaver 的全部目的,那么您也可以使用更简单的 FileSaver.js 库或手动创建 object url + link 从像 FileSaver.js 确实如此。
Object.assign(
document.createElement('a'),
{ href: URL.createObjectURL(blob), download: 'name.txt' }
).click()
此外,您不能使用 Angular 的 HTTP 服务,因为他们使用旧的 XMLHttpRequest
,并且它不能像 fetch
那样为您提供 ReadableStream来自 response.body
所以我的建议是简单地使用 Fetch API 代替。
我以前或多或少遇到过同样的情况。 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
在当前浏览器中是否可用。如果不是,则将 ponyfill
中的 WritableStream
直接分配给 streamSaver.WritableStream
属性.
由于前段时间遇到这个问题,所以我的解决方案只在Google Chrome 78、Firefox 70、Safari 13上进行了测试; web-streams-polyfill 2.0.5, and StreamSaver.js 2.0.3