前端实现有校验的大文件下载方案对比

项目需要实现下载大文件(最大1GB),并且下载时需要校验发起下载的用户是否有权限(最开始是token放在请求头这样做校验),所以想了以下这些方案。

1.blob生成url

既然要校验身份(token放请求头),就只能自己发起这个请求,用blob接收,然后生成url,再赋值给a标签的href属性,触发click事件,就会下载了。

需要注意响应头content-disposition的设置,设置为"attachment;xxx"才会下载,否则浏览器会先尝试打开/预览文件。或者给a标签设置download属性,值为文件名(如果不知道文件名可以设置download为空字符串,浏览器会尝试从响应头的content-disposition取文件名),这样浏览器也会直接下载而不是尝试打开文件。

但是这样肯定是有问题的:

  1. 大文件下载时响应很慢。我们是vue项目,http请求用axios发起,请求的then会在所有响应体都接收完成后才执行,意味着需要等到所有流接收完毕才会做下载,用户才会在页面上看到下载进程;
  2. 浏览器容易崩溃。执行到axios请求的then方法时,需要用blob变量接收,这些变量都是暂存在内存中的,而浏览器一般占用到1-2GB就会崩溃。

所以此方案打咩。

2.使用streamsaver.js

因为文件太大,自然会想到用流式下载的方式,避免占用过多内存。streamsaver.js就是实现前端流式下载的依赖包,可以解决下载大文件问题。 streamsaver原理可见:

图片来自:Http Fetch+StreamSaver.js分片下载大文件 - keitsi - 博客园

在开发时发现有一点小问题:因为项目运行环境无法访问github,而streamSaver的中间人mitm.html文件放在了github上,所以需要下载这个mitm.html文件放到项目public目录下,并修改引用地址。另外sw.js也需单独下载下来放到项目中。

解决后发现本地运行可行,浏览器不会崩溃啦!

但!是!部署后下载失败了! 现象是浏览器会新打开地址栏是xxx/mitm.html的窗口,并在控制台看到浏览器在不断地发起ping请求。(这里忘记截图了,自行想象)

发现是因为streamsaver使用了service worker,这个仅支持https或localhost,而我们部署是http,于是放弃此方案。

下图是支持的情况,如果不支持,在控制台输入ServiceWorker会报undefined错误。

3.使用file system Web API

是可以操作本地文件系统的API,但是研究了一番发现也是只支持https,放弃。

(不过这个没有仔细研究,不知道能否用这套API实现前端下载)

4.先校验再生成一次性下载链接

没辙了,还是从浏览器下载出发,想想咋做校验。最后想了这套方案:

  1. 用户点击下载后,先发起一个校验权限和身份的请求,如果通过校验,返回一个一次性的下载链接;
  2. window.open这个链接或者把这个链接给a标签并触发click。

一开始我们使用的是a标签的方式,但是如果请求的http status不是200、content-dispostion是json(就是请求失败了),浏览器还是会把这个错误响应信息下载为文件(我们用的是某个浏览器,不知道其他浏览器是不是聪明点)。

改为window.open方式的话,会新开一个标签页,响应成功了就会开始下载,并自动关闭这个新开的标签页。如果失败了,会把失败信息显示在页面。 不过还是出现了一些问题,因为后端接收到那个一次性下载请求后,需要对文件做很多处理,时间花费巨大,越大的文件越久,进而导致:

  1. 打开新页面后没有任何提示,需要等待很久才会显示下载;
  2. 经常失败,越大的文件越容易失败,而且现象居然是显示下载链接已过期,但是浏览器只显示了一个请求,后端查看日志却发现有两个请求。查看nginx错误日志发现了这样的报错: "connect() failed (111: Connection refused) while connecting to upstream, client: xxxxx, server: xxx, request: xxxx, upstream: xxx, host: xxx",意思是Nginx 尝试与 upstream(后端)建立 TCP 连接时,被目标机器立即拒绝,这条日志说明 Nginx 遇到了"网络层"错误,并可能触发 Nginx 向后端重试(注意:重试只在 Nginx→后端之间,浏览器仍只看到一个请求/响应)

第一个问题没办法缩短文件处理时间,只能把时间放到校验身份那个请求里。于是改为:

  1. 如果后端校验权限和身份成功,马上就开始处理文件,处理完成后才返回一次性下载连接,同时前端将这个请求的超时时间由原来的1.5分钟设置为半小时;
  2. 为了提升用户体验,制作了一个带有loading动画的等待下载页面,在用户点击下载后立马打开一个这个页面,等到响应一次性下载链接后,把这个链接赋值给地址栏,然后再做下载;
  3. 后端排查发现因为网关超时时间设置过小,才出现上述第2点问题,调大后就正常了。
相关推荐
携欢19 小时前
PortSwigger靶场之Stored XSS into HTML context with nothing encoded通关秘籍
前端·xss
小桥风满袖19 小时前
极简三分钟ES6 - const声明
前端·javascript
小小前端记录日常19 小时前
vue3 excelExport 导出封装
前端
南北是北北19 小时前
Flow 的 emit 与 tryEmit :它们出现在哪些类型、背压/缓存语义、何时用谁、常见坑
前端·面试
flyliu19 小时前
继承,继承,继承,哪里有家产可以继承
前端·javascript
司宸19 小时前
Cursor 编辑器高效使用与配置全指南
前端
维维酱19 小时前
为什么说 useCallback 实际上是 useMemo 的特例
前端·react.js
王六岁19 小时前
Vue 3 表单验证组合式 API,提供类似 Ant Design Vue Form 的强大表单验证功能
前端·vue.js
机构师19 小时前
<uniapp><日期组件>基于uniapp,编写一个自定义的日期组件
前端·javascript
lypzcgf19 小时前
Coze源码分析-资源库-创建提示词-前端源码
前端·人工智能·typescript·系统架构·开源软件·react·安全架构