我想从我的服务器下载一个加密文件,解密并保存到本地。我希望能够在文件下载的同时对其进行解密并将其写入本地,而不必等待下载完成后再进行解密并将解密后的文件放入锚点标签中。我这样做的主要原因是,在处理大文件时,浏览器不必在内存中存储数百兆或几个千兆字节的数据。
我想从我的服务器下载一个加密文件,解密并保存到本地。我希望能够在文件下载的同时对其进行解密并将其写入本地,而不必等待下载完成后再进行解密并将解密后的文件放入锚点标签中。我这样做的主要原因是,在处理大文件时,浏览器不必在内存中存储数百兆或几个千兆字节的数据。
只有结合服务工作线程、fetch和stream才能实现这一点。一些浏览器已经支持worker和fetch,但支持流式传输的fetch更少(Blink)。
new Response(new ReadableStream({...}))
我已经构建了一个流文件保存库,以便与服务工作线程通信,拦截网络请求:StreamSaver.js
它与node的stream略有不同,以下是一个示例:
function unencrypt(){
// should return Uint8Array
return new Uint8Array()
}
// We use fetch instead of xhr that has streaming support
fetch(url).then(res => {
// create a writable stream + intercept a network response
const fileStream = streamSaver.createWriteStream('filename.txt')
const writer = fileStream.getWriter()
// stream the response
const reader = res.body.getReader()
const pump = () => reader.read()
.then(({ value, done }) => {
let chunk = unencrypt(value)
// Write one chunk, then get the next one
writer.write(chunk) // returns a promise
// While the write stream can handle the watermark,
// read more data
return writer.ready.then(pump)
)
// Start the reader
pump().then(() =>
console.log('Closed the stream, Done writing')
)
})
你也可以通过xhr获得流式响应的另外两种方式,但这不是标准的,如果你不使用它们(responseType = ms-stream || moz-chunked-arrayBuffer),也没有关系,因为StreamSaver无论如何都依赖于fetch+ReadableStream,不能以其他方式使用
稍后当WritableStream + Transform streams也被实现时,你将能够像这样做
fetch(url).then(res => {
const fileStream = streamSaver.createWriteStream('filename.txt')
res.body
.pipeThrogh(unencrypt)
.pipeTo(fileStream)
.then(done)
})
值得一提的是,默认下载管理器通常与后台下载相关联,因此有些人在看到下载时会关闭选项卡。但这一切都发生在主线程中,所以当用户离开时需要警告用户。
window.onbeforeunload = function(e) {
if( download_is_done() ) return
var dialogText = 'Download is not finish, leaving the page will abort the download'
e.returnValue = dialogText
return dialogText
}
showSaveFilePicker
/FileSystemWritableFileStream
,自2020年底起在Chrome和所有主要衍生产品(包括Edge和Opera)中得到支持,并且作者提供了一个适配器(另一个主要答案的作者编写!),可用于Firefox和Safari,这将使您能够直接执行以下操作:async function streamDownloadDecryptToDisk(url, DECRYPT) {
// create readable stream for ciphertext
let rs_src = fetch(url).then(response => response.body);
// create writable stream for file
let ws_dest = window.showSaveFilePicker().then(handle => handle.createWritable());
// create transform stream for decryption
let ts_dec = new TransformStream({
async transform(chunk, controller) {
controller.enqueue(await DECRYPT(chunk));
}
});
// stream cleartext to file
let rs_clear = rs_src.then(s => s.pipeThrough(ts_dec));
return (await rs_clear).pipeTo(await ws_dest);
}
根据性能而定——例如,如果您要与MEGA竞争,您可能还要考虑修改DECRYPT(chunk)
以允许您使用ReadableStreamBYOBReader
:
...从底层字节源进行零拷贝读取。它用于从底层源高效地复制数据,其中数据作为“匿名”字节序列传递,例如文件。
出于安全原因,浏览器不允许将传入的可读流直接导入本地文件系统,因此您有两种解决方法:
window.open(Resource_URL)
:在新窗口中下载资源,并将Content_Disposition设置为“attachment”;<a download href="path/to/resource"></a>
:使用AnchorElement的“download”属性将流下载到硬盘中;希望这些能帮到您 :)
nodejs
实现。在这里,希望整个流程在提供下载文件之前完成并进行验证。如果一个100MB的文件最后一个字节损坏了怎么办? - guest271314