前端使用StreamSaver.js流式下载大文件

目前前端没有很好的api支持流式的文件的分片下载。如果直接把整个文件保存到Blob对象中再保存,有可能出现很多不可以预期的问题,可能会因为达到浏览器的Blob对象上限而下载失败。也有机会因为客户端内存太低而导致OOM。那如果我们有额外的文件服务器的话,可以选择把文件先导出到文件服务器,然后前端再通过文件路径由浏览器处理下载。但是如果又没有额外的文件服务器,又想要支持分片下载,这就是这篇文章的主题。

StreamSaver.js的工作原理

StreamSaver.js采用了不同的方法。现在,您可以创建一个直接到文件系统的可写流,而不是将数据保存在客户端存储或内存中(我说的不是chromes沙盒文件系统或任何其他网络存储)。这是通过模拟服务器如何指示浏览器使用某个响应头+服务工作者来保存文件来实现的 如果您试图保存的文件来自云/服务器,请使用服务器,而不是模拟浏览器使用StreamSaver在磁盘上保存文件的操作。添加那些额外的响应头,不要使用AJAX来获取它。FileSaver有一个很好的关于使用头的wiki。如果您无法更改标题,那么您可以使用StreamSaver作为最后手段。FileSaver、streamsaver和其他类似的应用程序主要用于浏览器内客户端生成的内容。

StreamSaver.js通过伪造一个服务器文件的链接,伪造的服务器收文件下载到请求返回Content-Disposition头告诉浏览器开始下载文件。但实际上这个文件服务器并不存在并且内容也不在服务器上。因此,他的解决方案是创建一个Service Worker(sw.js),它可以拦截请求并使用responsdWith()伪装成服务器。

中间人MITM

既然是伪造的服务器,那必然涉及到中间人,默认StreamSaver.js的MITM是https://jimmywarting.github.io/StreamSaver.js/mitm.html?version=2.0.0。

如果你的客户端是联网的而且是可以访问github那没有问题,那如果你的站点是内网或客户端根本无法访问github那问题就来了,下载根本无法触发。

 在JavaScript中,MITM代表"Man-in-the-Middle",这是一种网络攻击技术。在这种攻击中,攻击者秘密地插入他们自己的设备或软件,从而在一个两方或多方的通信中间接收、修改、甚至拦截消息。这种攻击可以在没有任何一方知道的情况下进行。在JavaScript中,你可能会在谈到网络安全性时听到MITM,尤其是在处理如HTTPS这样的安全协议时。在这些情况下,JavaScript可能会使用一些API或技术(例如Service Workers)来尝试防止或检测MITM攻击。

自部署MITM:

  • 下载文件
  1. https://github.com/jimmywarting/StreamSaver.js/blob/master/mitm.html
  2. https://github.com/jimmywarting/StreamSaver.js/blob/master/sw.js

 

  • 把下载的文件放入项目目录

目录:./public

 

StreamSaver.js整合

  • 安装
npm install streamsaver --save
  • 引入依赖
import * as streamSaver from 'streamsaver'
  • 下载方法封装
复制代码
 
export async function download(url, parameters, fileName) {
  streamSaver.mitm = 'https://xxxx/mitm.html?version=2.0.0'
  const fileStream = streamSaver.createWriteStream(fileName)
  return fetch(url, {
    method: 'POST',
    body: JSON.stringify(parameters),
    cache: 'no-cache',
    headers: {
      'Content-Type': 'application/json'
    }
  }).then(res => {
    const readableStream = res.body
    if (window.WritableStream && readableStream.pipeTo) {
      return readableStream.pipeTo(fileStream)
    }
    window.writer = fileStream.getWriter()
    const reader = res.body.getReader()
    const pump = () => reader.read()
      .then(res => res.done
        ? window.writer.close()
        : window.writer.write(res.value).then(pump))
    pump()
  })
}
 
复制代码
  • MITM设置

本地调试,设置中间人为localhost地址:
streamSaver.mitm = 'http://localhost:9527/mitm.html?version=2.0.0'
线上设置中间人为生产地址:
streamSaver.mitm = 'https://xxxx/mitm.html?version=2.0.0'

 

参考:

 

出处:https://www.cnblogs.com/keitsi/p/17715177.html

补充:

需求:分段从后端获取文件blob数据,使用streamSaver下载到本地

  • 安装
npm install streamsaver --save
  • 引入依赖
import * as streamSaver from 'streamsaver'
  • 下载方法封装(streamDownload.js)
复制代码
 
 // 假设后端支持通过 Range 请求头进行流式传输
 import as streamSaver from 'streamsaver'
 import { downloadFileAPI } from '@/api/XXXXX.js'
 import { Message } from 'element-ui'
 ​
 export function streamDownloadAction (file) {
   Message.success('文件下载中')
   const fileName file.name
   const totalSize Number(file.size)
   const fileStream streamSaver.createWriteStream(fileName)
   const writer fileStream.getWriter()
   let downloadedSize 0
   const chunkSize 1024 1024 10// 每块10MB
   function fetchNext () {
     if (downloadedSize totalSize) {
       // 设置 range 以获取文件的下一个分块
       const end Math.min(downloadedSize chunkSize 1, totalSize 1)
 ​
       const paramsData = {
         name: file.name,
         path: file.path,
         storageConfigId: file.storageConfigId,
         range: `bytes=${downloadedSize}-${end}`
      }
 ​
       downloadFileAPI(paramsData).then(response => {
         // 使用 new FileReader(),将 blob 转为 json,然后进行处理
         const filereader new FileReader()
         filereader.readAsText(response)
         filereader.onloadend = (result) => {
           try {
             const jsonErrorMessage JSON.parse(result.target.result)
             // 解析对象成功,说明是json数据
             console.log('接口返回的 JSON 错误信息:', jsonErrorMessage)
             Message.error('下载失败')
             writer.abort()
          } catch (error) {
             // 解析成对象失败,说明是正常的文件流
             downloadedSize end 1
             // 获取文件块内容并写入文件流
             const readableStream response.stream()
             const reader readableStream.getReader()
             // eslint-disable-next-line no-inner-declarations
             function push () {
               reader.read().then(({ done, value }) => {
                 if (done) {
                   // 检查是否还有更多分块要下载
                   if (downloadedSize >= totalSize) {
                     // 所有分块都已下载完成,关闭 StreamSaver 写入流
                     writer.close()
                  } else {
                     // 还有更多分块,继续下载下一个分块
                     fetchNext()
                  }
                   return
                }
                 // 将数据推送到写入流中
                 writer.write(value)
                 // 继续读取下一个数据块
                 push()
              }).catch(error => {
                 console.error('Stream error:', error)
                 // controller.error(error)
                 writer.abort()
              })
            }
             // 开始读取流数据
             push()
          }
        }
      }).catch(() => {
         Message.error('下载失败')
         writer.abort()
      })
    } else {
       fetchNext()
    }
  }
   // 开始下载第一个分块
   fetchNext()
 }
复制代码
  • 封装方法调用
复制代码
  // 引入方法
 import { streamDownloadAction } from '@/utils/streamDownload.js'

        // 分段下载

     // StreamSaver.js 将大文件拆分成小块,并在下载过程中逐块传输到硬盘,从而降低内存占用和提高下载速度

     downloadFile (file) {
       // console.log('downloadFile=', file)
       this.currentFile Object.assign({}, file)
       this.$confirm(`是否确认下载 “ ${file.name” ?`, '提示', {
         confirmButtonText: '确定',
         cancelButtonText: '取消',
         type: 'warning'
      }).then(() => {
         streamDownloadAction(file)
      })
    },
 
javascript
 
复制代码
posted @   丫丫learning  阅读(36)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 推荐几款开源且免费的 .NET MAUI 组件库
· 实操Deepseek接入个人知识库
· 易语言 —— 开山篇
· 【全网最全教程】使用最强DeepSeekR1+联网的火山引擎,没有生成长度限制,DeepSeek本体
点击右上角即可分享
微信分享提示